feat: 기동·종료·헬스체크 견고성 보강
운영에서 조용히 문제를 일으킬 수 있는 지점들을 정리한다. - env 검증(fail-fast): 설정이 잘못되면 서버가 뜨지 않는다. 운영에서 DB_* 누락과 'change-me' 같은 예시 값 배포를 차단하고, 문제를 하나씩이 아니라 한 번에 모아 보여준다. DB_PASSWORD 가 없을 때 'password' 로 조용히 동작하던 폴백도 제거. - graceful shutdown: SIGTERM 시 요청·커넥션을 정리한 뒤 종료한다. 종료 로그는 OnApplicationShutdown 훅으로 남긴다 — process.on(signal) 로 직접 잡으면 Nest 의 종료 흐름에 간섭해 종료 코드가 달라진다. - 헬스체크 두 가지 결함 수정: pingCheck 기본 타임아웃 1초가 짧아 유휴 커넥션 재연결 시 정상 DB 를 down 으로 오판했고, 실패 응답이 SYS_001 로 덮여 어느 구성요소가 죽었는지 알 수 없었다. 모니터링이 읽는 엔드포인트이므로 표준 래퍼·에러 코드에서 제외한다. - Rate limiting 을 @nestjs/throttler 로 일원화 (express-rate-limit 제거). 두 리미터가 겹쳐 헤더의 한도와 실제 동작이 어긋나 있었다. - e2e 를 응답 계약 검증으로 교체. 기존 테스트는 없는 경로를 검사해 이미 실패 상태였다. - 로깅 규칙(logging.md) 신설 — 레벨 기준·민감정보 마스킹·traceId Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+20
-22
@@ -4,16 +4,17 @@ import { Logger, RequestMethod, ValidationPipe } from '@nestjs/common';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { WinstonModule } from 'nest-winston';
|
||||
import helmet from 'helmet';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import type {
|
||||
Express,
|
||||
Request as ExpressRequest,
|
||||
Response as ExpressResponse,
|
||||
} from 'express';
|
||||
|
||||
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
|
||||
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
|
||||
import { winstonConfig } from './shared/logger/winston.config';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
|
||||
import { HttpExceptionFilter } from '@/common/filters/http-exception.filter';
|
||||
import { TransformInterceptor } from '@/common/interceptors/transform.interceptor';
|
||||
import { winstonConfig } from '@/shared/logger/winston.config';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
@@ -54,28 +55,17 @@ async function bootstrap() {
|
||||
if (!origin || whitelist.indexOf(origin) !== -1) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
// 클라이언트에는 403 금지 에러로 리턴됨
|
||||
callback(new Error('CORS 정책에 의해 차단된 도메인입니다.'));
|
||||
// Error 를 넘기면 500(SYS_001)이 되어 원인 파악이 어려우므로,
|
||||
// CORS 헤더를 부여하지 않아 브라우저가 차단하도록 한다.
|
||||
callback(null, false);
|
||||
}
|
||||
},
|
||||
credentials: true, // 쿠키 교환 허용
|
||||
});
|
||||
|
||||
// 3. 글로벌 Rate Limiting 적용 (DDoS, 브루트포스 예방)
|
||||
app.use(
|
||||
rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15분
|
||||
max: 150, // 15분 IP당 최대 150개 요청
|
||||
message: {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'SYS_001',
|
||||
message:
|
||||
'일시적인 시스템 오류가 발생했습니다. (요청 한도 초과) 잠시 후 다시 시도해 주세요.', // 글로벌 통합 에러 포맷
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
// 3. Rate Limiting 은 AppModule 의 ThrottlerGuard(APP_GUARD)로 일원화되어 있다.
|
||||
// 라우트 단위 제어(@Throttle / @SkipThrottle)가 가능하고, 한도 초과는 REQ_001(429)로 응답된다.
|
||||
// (전역 예외 필터가 429 → REQ_001 로 매핑한다 — .claude/rules/error-codes.md)
|
||||
|
||||
// 4. 전역 DTO 유효성 파이프 (강력한 검증 단계)
|
||||
app.useGlobalPipes(
|
||||
@@ -88,7 +78,7 @@ async function bootstrap() {
|
||||
|
||||
// 5. 프론트엔드 연동을 위한 규격화된 에러 & 성공 페이로드 인터셉터 적용
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
app.useGlobalInterceptors(new TransformInterceptor());
|
||||
app.useGlobalInterceptors(new TransformInterceptor(app.get(Reflector)));
|
||||
|
||||
// 6. Swagger API 문서 설정
|
||||
const config = new DocumentBuilder()
|
||||
@@ -100,6 +90,13 @@ async function bootstrap() {
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api-docs', app, document);
|
||||
|
||||
// 7. Graceful shutdown — SIGTERM 수신 시 새 요청을 받지 않고
|
||||
// 처리 중인 요청을 마친 뒤 DB/Redis 커넥션을 정리하고 종료한다.
|
||||
// (이 설정이 없으면 배포·재시작 때 처리 중이던 요청이 그대로 끊긴다)
|
||||
app.enableShutdownHooks();
|
||||
|
||||
// 종료 로그는 ShutdownLogger(OnApplicationShutdown)가 남긴다.
|
||||
|
||||
const port = Number(process.env.PORT ?? 3000);
|
||||
await app.listen(port, '0.0.0.0');
|
||||
Logger.log(`🚀 Backend running on http://localhost:${port}/api`, 'Bootstrap');
|
||||
@@ -108,4 +105,5 @@ async function bootstrap() {
|
||||
'Bootstrap',
|
||||
);
|
||||
}
|
||||
|
||||
void bootstrap();
|
||||
|
||||
Reference in New Issue
Block a user