NestJS 개발 패턴
모듈식 TypeScript 백엔드를 위한 프로덕션급 NestJS 패턴입니다.
활성화 시기
- NestJS API 또는 서비스 구축
- 모듈, 컨트롤러 및 프로바이더 구조화
- DTO 검증, 가드, 인터셉터 또는 예외 필터 추가
- 환경 인식 설정 및 데이터베이스 통합 구성
- NestJS 단위 또는 HTTP 엔드포인트 테스트
프로젝트 구조
src/
├── app.module.ts
├── main.ts
├── common/
│ ├── filters/
│ ├── guards/
│ ├── interceptors/
│ └── pipes/
├── config/
│ ├── configuration.ts
│ └── validation.ts
├── modules/
│ ├── auth/
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── dto/
│ │ ├── guards/
│ │ └── strategies/
│ └── users/
│ ├── dto/
│ ├── entities/
│ ├── users.controller.ts
│ ├── users.module.ts
│ └── users.service.ts
└── prisma/ 또는 database/
- 도메인 코드는 기능 모듈 내에 유지하세요.
- 크로스커팅 필터, 데코레이터, 가드, 인터셉터는
common/에 넣으세요. - DTO는 해당 모듈 근처에 두세요.
부트스트랩 및 글로벌 검증
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
}),
);
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
- 공개 API에서는 항상
whitelist와forbidNonWhitelisted를 활성화하세요. - 라우트마다 검증 구성을 반복하는 대신 하나의 글로벌 검증 파이프를 사용하는 것이 좋습니다.
모듈, 컨트롤러 및 프로바이더
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
getById(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.getById(id);
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
}
@Injectable()
export class UsersService {
constructor(private readonly usersRepo: UsersRepository) {}
async create(dto: CreateUserDto) {
return this.usersRepo.create(dto);
}
}
- 컨트롤러는 얇게 유지하세요: HTTP 입력을 파싱하고, 프로바이더를 호출하고, 응답 DTO를 반환합니다.
- 비즈니스 로직은 인젝터블 서비스에 넣고 컨트롤러에는 넣지 마세요.
- 다른 모듈이 실제로 필요한 프로바이더만 내보내세요.
DTO 및 검증
export class CreateUserDto {
@IsEmail()
email!: string;
@IsString()
@Length(2, 80)
name!: string;
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}
- 모든 요청 DTO는
class-validator로 검증하세요. - ORM 엔티티를 직접 반환하는 대신 전용 응답 DTO 또는 시리얼라이저를 사용하세요.
- 비밀번호 해시, 토큰 또는 감사 컬럼과 같은 내부 필드가 유출되지 않도록 하세요.
인증, 가드 및 요청 컨텍스트
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin/report')
getAdminReport(@Req() req: AuthenticatedRequest) {
return this.reportService.getForUser(req.user.id);
}
- 인증 전략과 가드는 진정으로 공유되지 않는 한 모듈 로컬로 유지하세요.
- 가드에서 거친 접근 규칙을 인코딩하고, 서비스에서 리소스별 권한 부여를 수행하세요.
- 인증된 요청 객체에는 명시적 요청 타입을 선호하세요.
예외 필터 및 오류 형태
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse<Response>();
const request = host.switchToHttp().getRequest<Request>();
if (exception instanceof HttpException) {
return response.status(exception.getStatus()).json({
path: request.url,
error: exception.getResponse(),
});
}
return response.status(500).json({
path: request.url,
error: 'Internal server error',
});
}
}
- API 전반에 걸쳐 일관된 오류 봉투를 유지하세요.
- 예상되는 클라이언트 오류에는 프레임워크 예외를 던지고, 예상치 못한 실패는 중앙에서 로깅하고 래핑하세요.
설정 및 환경 변수 검증
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
validate: validateEnv,
});
- 환경 변수는 부트 시점에 검증하고, 첫 번째 요청 시 지연 검증하지 마세요.
- 설정 접근은 타입이 지정된 헬퍼나 설정 서비스 뒤에 유지하세요.
- 기능 코드 전체에서 분기하지 않고 설정 팩토리에서 개발/스테이징/프로덕션 구성을 분리하세요.
지속성 및 트랜잭션
- 리포지토리/ORM 코드는 도메인 언어를 사용하는 프로바이더 뒤에 유지하세요.
- Prisma 또는 TypeORM의 경우, 작업 단위를 소유하는 서비스에서 트랜잭션 워크플로를 격리하세요.
- 컨트롤러가 다단계 쓰기를 직접 조정하지 못하게 하세요.
테스트
describe('UsersController', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [UsersModule],
}).compile();
app = moduleRef.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.init();
});
});
- 프로바이더는 모킹된 종속성으로 격리하여 단위 테스트하세요.
- 가드, 검증 파이프 및 예외 필터에 대한 요청 수준 테스트를 추가하세요.
- 프로덕션에서 사용하는 것과 동일한 글로벌 파이프/필터를 테스트에서도 재사용하세요.
프로덕션 기본값
- 구조적 로깅 및 요청 상관관계 ID를 활성화하세요.
- 환경 변수/구성이 잘못된 경우 부분적으로 부팅하지 말고 종료하세요.
- 명시적 헬스 체크와 함께 DB/캐시 클라이언트에 대해 비동기 프로바이더 초기화를 선호하세요.
- 백그라운드 작업 및 이벤트 컨슈머는 HTTP 컨트롤러 내부가 아닌 자체 모듈에 유지하세요.
- 공개 엔드포인트에 대해 비율 제한, 인증 및 감사 로깅을 명시적으로 설정하세요.