ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • NestJS-Request lifecycle
    JavaScript 2022. 3. 26. 18:43

    Request lifecycle

    1. Incoming request
    2. Middleware - Globally bound middleware / Module bound middleware
    3. Guards - Global guards / Controller guards / Route guards
    4. Interceptors (pre-controller) - Global interceptors / Controller interceptors / Route interceptors
    5. Pipes - Global pipes / Controller pipes / Route pipes / Route parameter pipes
    6. Controller (method handler)
    7. Service (if exists)
    8. Intercepter (post-request) - Route interceptor / Controller interceptor / Global interceptor
    9. Exception filters (route, then controller, then global)
    10. Server response

    Middleware

    • 라우터 핸들러가 호출되기 전에 실행되며, 요청/응답 객체, next함수에 접근 가능
    ./middlewares/logger.middleware.ts
    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(req.ip);
            next();
        }
    }
    
    
    ./app.module.ts
    export class AppModule {
        configure(consumer: MiddlewareConsumer) {
            consumer.apply(LoggerMiddleware).forRoutes('*');
        }
    }

    Guards 

    • 하나의 책임(목적)
    • 런타임에서 요청을 라우터로 보내줄건지(조건-허가,역할 등에 따라)
    • 보통 인증에 사용됨
    • 익스프레스에서는 보통 미들웨어에서 인증을 처리했었음
    • 미들웨어로 처리하는 것도 나쁘진 않음. 토큰 검증 및 유저 데이터들을 요청에 추가하는 것은 특정 라우터에 연결되는 것이 아니기에..)
    • 하지만 미들웨어는 next() 함수를 호출 한 뒤에 어떤 핸들러가 실행될지 모름
    • Guards는 실행컨텍스트 인스턴스에 접근가능(필터,파이프,인터셉터처럼)
    @UseGuards(JwtAuthGuard)
    @Get('/profile')
        getProfile(@Request() req) {
            return req.user;
    	} // JwtAuthGuard 에서 검증되면 JwtStrategy을 통해 req.user에 넣어줌
    
    @Injectable()
    export class JwtAuthGuard extends AuthGuard('jwt') {
        canActivate(context: ExecutionContext) {
            return super.canActivate(context);
        }
    
        handleRequest(err, user, info) {
            // passport - authguard jwt 에 의해 토큰 검증
            // user에서 jwt strategy의 validate에 의해 검증 후 반환되는 유저 데이터임
            if (err || !user) {
                throw err || new UnauthorizedException();
            }
            return user;
        }
    }
    
    @Injectable()
    export class JwtStrategy extends PassportStrategy(Strategy) {
        constructor() {
            super({
                jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
                ignoreExpiration: false,
                secretOrKey: jwtConstants.secret,
            });
        }
    
        async validate(payload: any) {
            return {
                userId: payload.sub,
                username: payload.username,
                email: payload.email,
                test: 'test',
            };
        }
    }
    
    // Auth module에서 JWT모듈을 IMPORT 해야 위와같이 로직이 진행됨
    @Module({
        imports: [
            forwardRef(() => UsersModule),
            PassportModule,
            JwtModule.register({
                secret: jwtConstants.secret,
                signOptions: { expiresIn: jwtConstants.expiresIn },
            }),
        ],
        controllers: [AuthController],
        providers: [AuthService, JwtStrategy], ## JwtModule에서 해당 JWTStradtegy를 사용
        exports: [AuthService],
    })
    export class AuthModule {}

     

    Interceptor - 라우터 전/후에 실행

    • 미들웨어 다음에 실행되며 라우터 전(아래에서와 같이 console.log('Before...')(에러 발생하지 않은다면 console.log('After...'))에 실행됨
    • 특정 함수 실행 전후로 로직 추가 
    • 함수에서 반환된 결과 변환
    • 함수에서 thrown 된 에러 변환
    • 특정 조건에 따른 함수 오버라이드(캐시 목적 등.)

     

    @UseInterceptors(LoggingInterceptor)
    async login(@Body() dto: UserLoginDto): Promise<string> {
            return;
        }
    
    @Injectable()
    export class LoggingInterceptor implements NestInterceptor {
        intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
            console.log('Before...');
    
            const now = Date.now();
            return next
                .handle()
                .pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
        }
    }

    Pipes - 요청에 담긴 데이터를 검증/변환을 위함

    • transformation: transform input data to the desired form (e.g., from string to integer)
    • validation: evaluate input data and if valid, simply pass it through unchanged; otherwise, throw an exception when the data is incorrect
    @Controller('users')
    export class UsersController {
        constructor(private readonly usersService: UsersService) {}
    
    	@Get(':id')
        findOne(@Param('id', ParseIntPipe) id: number) {
            return this.usersService.findOne(+id);
        }
    }

    Exception Filters - 마지막 단계인 서버 응답의 바로 전

    @UseFilters(new HttpExceptionFilter())
    @Get()
    findAll(
    ) {
        throw new HttpException('error', 400);
    }
    
    // ./filters/http-exception.filter.ts
    @Catch(HttpException)
    export class HttpExceptionFilter implements ExceptionFilter {
        catch(exception: HttpException, host: ArgumentsHost) {
            // 컨트롤러의 라우터 로직이 실행되고 에러(throw new HttpException('error', 400))가 throw되면 실행됨
            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,
            });
        }
    }

    'JavaScript' 카테고리의 다른 글

    NestJS 공부하기  (0) 2022.03.15
    NodeJS - Express, file structure  (0) 2022.02.19
    Docker - Express(TS) - Config(Prod,dev,test)  (0) 2022.01.30
    빌트인 객체, 래퍼 객체  (0) 2021.12.17
    변수(var, let, const) + feat.메모리  (0) 2021.12.16
Designed by Tistory.