본문 바로가기
Today I Learned/DevCamp

데브캠프 2일차

by 아보_ 2024. 3. 5.
반응형
스파르타 데브캠프를 통해 하루하루 공부한 내용을 정리합니다.

 

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