스파르타 데브캠프를 통해 하루하루 공부한 내용을 정리합니다.
NestJS
Typescript의 extends 와 implements
extends
extends 키워드는 한 클래스가 다른 클래스를 상속받을 때 사용됩니다. 이를 통해 부모 클래스의 속성과 메소드를 자식 클래스가 상속받아 사용할 수 있게 됩니다. 또한, extends는 인터페이스가 다른 인터페이스를 확장할 때에도 사용될 수 있습니다.
예시코드
class Animal {
move(distanceInMeters: number): void {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark(): void {
console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
Dog 클래스는 Animal 클래스로부터 move 메소드를 상속받습니다. Dog 클래스는 bark 메소드도 가지고 있어, Dog의 인스턴스는 두 메소드 모두 사용할 수 있습니다.
implements
implements 키워드는 클래스가 특정 인터페이스를 구현할 때 사용됩니다. 이는 클래스가 인터페이스에 정의된 모든 속성과 메소드를 구현하도록 강제합니다. 인터페이스는 구체적인 구현을 제공하지 않고, 클래스가 따라야 할 "계약"만을 정의합니다.
예시코드
interface IAnimal {
move(distanceInMeters: number): void;
}
class Cat implements IAnimal {
move(distanceInMeters: number): void {
console.log(`Cat moved ${distanceInMeters}m.`);
}
}
const cat = new Cat();
cat.move(5);
Cat 클래스는 IAnimal 인터페이스를 구현합니다. 따라서 Cat 클래스는 IAnimal 인터페이스에 정의된 move 메소드를 구현해야 합니다. implements를 사용하면 클래스가 특정 인터페이스의 규약을 준수하도록 강제할 수 있으며, 이는 코드의 일관성과 가독성을 높이는 데 도움이 됩니다.
extends는 상속을 위해 사용되며, 클래스가 다른 클래스의 속성과 메소드를 상속받을 수 있게 하거나, 인터페이스가 다른 인터페이스를 확장할 수 있게 합니다.
implements는 구현을 위해 사용되며, 클래스가 인터페이스의 규약을 준수하도록 강제합니다.
각각의 사용은 코드의 재사용성을 높이고, 코드의 구조를 명확하게 하며, 유지보수를 용이하게 하는 데 기여합니다.
오늘의 구현내용
Swagger 적용
1. 모듈 설치
yarn add @nestjs/swagger
2. swagger.ts 작성 ( src/common/config/swagger.ts )
import { INestApplication } from "@nestjs/common";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
export function setSwagger(app: INestApplication): void {
const config = new DocumentBuilder()
.setTitle('DevCamp')
.setDescription('DevCamp API docs 입니다.')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
}
3. main.ts에 setSwagger 추가
async function bootstrap() {
const app = await NestFactory.create(AppModule);
setSwagger(app);
await app.listen(3000);
}
그 동안 swagger 적용코드를 main.ts에 그대로 작성해왔었는데, 이번에 예시코드를 보고 따로 관리하여 main.ts를 간결하게 유지할 수 있다는 것을 느껴 참고하여 적용시켰습니다.
Request Logger 구현
1. logger.middleware.ts 구현 ( src/common/middlewares/logger.middleware.ts )
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
private logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction) {
this.logger.log(
`\n[REQUEST] ${req.ip} ${req.method} ${res.statusCode} ${
req.originalUrl
}\n[BODY] ${JSON.stringify(req.body, null, 2)}\n[QUERY] ${JSON.stringify(
req.query,
null,
2,
)}`,
);
next();
}
}
2. app.module에 LoggerMiddleware 전역사용으로 설정
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}
3. 테스트
@Post('test/logger')
logTest(
@Body() body: { test: string; logger: string },
@Query('name') name: string,
): string {
return this.appService.getHello();
}
SuccessInterceptor 구현
1. success.interceptor.ts 구현 ( src/common/interceptors/succes.interceptor.ts )
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
@Injectable()
export class SuccessInterceptor<T> implements NestInterceptor<T, any> {
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
return next.handle().pipe(
map(data => ({
success: true,
data,
}))
);
}
}
2. app.module에 successinterceptor 의존성 주입
providers: [
AppService,
{ provide: APP_INTERCEPTOR, useClass: SuccessInterceptor },
],
3. 결과
기존의 응답값은 data에 담기며 success: true와 함께 응답
Logger에 Response 정보 추가
1. logger.middleware.ts 수정
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
private logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction) {
res.on('finish', () => {
this.logger.log(
`[RESPONSE] ${req.ip} ${req.method} ${res.statusCode} ${req.originalUrl}`,
);
});
this.logger.log(
`[REQUEST] ${req.ip} ${req.method} ${
req.originalUrl
}\n[BODY] ${JSON.stringify(req.body, null, 2)}\n[QUERY] ${JSON.stringify(
req.query,
null,
2,
)}`,
);
next();
}
}
2. 결과
res.on('finish')로 로그를 한번에 찍지 않고 REQUEST로그와 RESPONSE로그로 나눈 이유
기존에는 응답시에 요청정보들과 응답정보를 로깅했었다. 요청이 성공하던, 실패하던 응답을 한다면 정상적으로 res.on('finish)로 로깅이 되었지만, 응답을 하기 전 서버가 중단되는 오류가 발생하면(이런일은 없어야겠지만.. 직전 회사에서 처음 앱 출시당시 서버가 중단되는 오류가 종종 있었다..) 정확히 어떤요청(endpoint, ip 등등)에서 오류가 발생했는지 알 수 없었다(물론, 서버내에 에러로그로 유추는 가능했다). 그래서 이번에 하루종일 고민하다가 결국 요청로그와 응답로그를 분리하는 결정을 했다.
HttpExceptionFilter 구현
1. http.exception.filter 구현 ( src/common/filter/http.exception.filter.ts )
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
private logger = new Logger('HTTP');
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const res = ctx.getResponse<Response>();
const req = ctx.getRequest<Request>();
const status = exception.getStatus();
const error = exception.getResponse() as
| string
| { error: string; statusCode: number; message: string | string[] };
if (typeof error === 'string') {
res.status(status).json({
success: false,
data: {
error,
},
});
this.logger.error(
`[EXCEPTION FILTER] ${req.ip} ${req.method} ${req.originalUrl}\n[ERROR] ${error}`,
);
} else {
res.status(status).json({
success: false,
data: {
...error,
},
});
this.logger.error(
`[EXCEPTION FILTER] ${req.ip} ${req.method} ${req.originalUrl}\n[ERROR] ${error.message} ${error.error}`,
);
}
}
}
2. main.ts 에 전역사용 설정
app.useGlobalFilters(new HttpExceptionFilter());
'Today I Learned > DevCamp' 카테고리의 다른 글
20240309(토) (0) | 2024.03.10 |
---|---|
데브캠프 5일차 (0) | 2024.03.09 |
데브캠프 4일차 (0) | 2024.03.08 |
데브캠프 3일차 (0) | 2024.03.07 |
데브캠프 1일차 (0) | 2024.03.04 |