Nest js 심화

강준호·2024년 2월 21일

NestJs

목록 보기
2/6

모듈 컨트롤러 프로바이더는 외우는게 좋아

미들웨어

공식문서를 참고하자. 외우지 말고.

https://docs.nestjs.com/middleware


// 미들웨어의 세팅 (클래스) 추천!
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

// 미들웨어의 세팅 (함수도 가능하긴함)



//app.module 에서 연결! (추천)!

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('*'); //전체(*) 라우터에 대해 하겠다.
  }
}

// main.ts 의 app.use() 로 연결

async function bootstrap() {
  const app = await NestFactory.create<INestApplication<Express>>(AppModule);
  app.use(loggerMiddleware);
  await app.listen(5050);
}
bootstrap();
  • main.ts 의 app.use() 로 연결은 함수형만 가능하다. 클래스의 인스턴스는 불가.

auth.middleware.ts (여권검사)

import {
  BadRequestException,
  Injectable,
  NestMiddleware,
  UnauthorizedException,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { ParamsDictionary } from 'express-serve-static-core';
import { verify } from 'jsonwebtoken';
import { ParsedQs } from 'qs';
import { PrismaService } from './../db/prisma/prisma.service';

const JWT_SECRET_KEY = process.env.JWT_SECRET_KEY;
if (!JWT_SECRET_KEY) throw new BadRequestException('No JWT_SECRET_KEY');

@Injectable()
export class AuthMiddleware implements NestMiddleware<Request, Response> {
  constructor(private readonly prismaService: PrismaService) {}

  async use(
    req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>,
    res: Response<any, Record<string, any>>,
    next: (error?: any) => void,
  ) {
    req.user = null;

    const accessToken = req.headers.authorization?.split('Bearer ')[1];
    if (!accessToken) return next();

    let id: number;

    try {
      const { sub } = verify(accessToken, process.env.JWT_SECRET_KEY);
      id = Number(sub);
    } catch (e) {
      throw new UnauthorizedException('Invalid AccessToken'); // 이거를 써야 filter 에서 걸려
      //   throw new Error('Invalid AccessToken');
    }

    const user = await this.prismaService.user.findUnique({
      where: { id: Number(id) },
    });

    if (!user) throw new BadRequestException('No User');
    req.user = user;

    next();

    //인증관련 처리
    // => 여권검사 == 토큰검사
    //(토큰 있는지 확인
    //=> 없다? => 일단 통과 but 아무것도 안해줌.
    // => 있다? => 토큰 유효한지 확인
    // => 유효 X? => 돌아가~ 에러 발생
    // => 토큰 유효 => 유저 가져옴  => 유저 있나?
    // => 유저 X? => 에러발생
    // => 유저 O => 이후 프로세스에서 유저를 사용할 수 있도록 req.user 넣어준다.
  }
}

후에 모듈에 연결해준다

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(AuthMiddleware).forRoutes('*');
  }
}

가드

https://docs.nestjs.com/guards

@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}

@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}

두개의 방식 차이.

  • 인스턴스화를 하면 인자로 여러개를 받을 수 있어.

데코레이터의 role 에 따라 연계하는 가드 구성 canActivate

//공식 문서 Roles.guard.ts

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Roles } from './roles.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get(Roles, context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}


// LoggedInOnly.guard
@Injectable()
export class LoggedInOnlyGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const loggedInOnly = this.reflector.get<boolean>(
      'LoggedInOnly',
      context.getHandler(),
    ); //
    if (!loggedInOnly) return true; // 다 통과

    const request = context.switchToHttp().getRequest<Request>();

    if (!request.user) throw new ForbiddenException();

    return true;
  }
}

예외

https://docs.nestjs.com/exception-filters

예외 필터

  • 기본(내장) 예외 필터가 자동으로 많은 사례를 처리할 수 있지만 예외 레이어에 대한 완전한 제어가 필요할 수 있습니다.
  • 예를 들어 로깅을 추가하거나 일부 동적 요소를 기반으로 다른 JSON 스키마를 사용할 수 있습니다.
//http-Exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}
  • 클래스의 인스턴스인 예외를 포착하고 HttpException이에 대한 사용자 정의 응답 논리를 구현하는 예외 필터

=> 후에 전역으로 사용하기 위해 main.ts 에 연결

//main.ts
  app.useGlobalFilters(new HttpExceptionFilter());

원하는 컨트롤러에만 적용할 수도 있다.

import { Controller, UseFilters } from '@nestjs/common';
import { HttpExceptionFilter } from './path/to/http-exception.filter'; // Update the path as necessary

@Controller('some-path')
@UseFilters(HttpExceptionFilter)
export class SomeController {
  // Controller methods
}

인터셉터

  • Ex) res 성공 했을때 성공했다는 틀을 매번 써줄 수 없으니, 인터셉터로 한꺼번에 처리한다.

response 매핑 인터셉터

// 공식문서 transform.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  data: T;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
    return next.handle().pipe(map(data => ({ data })));
  }
}

(rxjs)

=> 비동기적 요청들을 배열로 바꿔주는, ex) 5번째 순서에 뭐해주기 이런것들.

//transform.interceptor.ts data 반환 형식을 바꿔준것

import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  success: true;
  result: T;
  error: null;
}

@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, Response<T>>
{
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next.handle().pipe(
      map((data) => ({
        success: true,
        result: data,
        error: null,
      })),
    );
  }
}

후에 전역 인터셉터로 사용하기 위해 main.ts 에 연결

///main.ts
app.useGlobalInterceptors(new TransformInterceptor());

커스텀 데코레이터

https://docs.nestjs.com/security/authentication

데코레이터 팩토리

import { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
  • 데코레이터 만든 후에, Gaurd 는 전역으로 사용하고, 데코레이터 달고있으면 pass 실행해주게.

공부해야할것

  • include 를 언제 쓰는가.

0개의 댓글