네스트에서 요청의 흐름은 다음과 같다.
요청 -> 미들웨어 -> 가드 -> 인터셉터 -> 파이프 -> 컨트롤러(+ 서비스 & 저장소) -> 인터셉터 -> 예외필터 -> 응답
아래와 같이 사용하는 것이 바람직하다. (뇌피셜 주의)
Middleware functions in NestJS are used to intercept incoming HTTP requests and outgoing responses before they reach their final destination. Middleware is ideal for adding logging, authentication, or authorization functionality to your application.
Some use cases for middleware include:
Logging: Middleware can be used to log request and response data for debugging and auditing purposes.
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(`[${new Date().toLocaleString()}] ${req.method} ${req.url}`);
next();
}
}
Authentication: Middleware can be used to authenticate incoming requests before they reach the controller.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
// Check if the request contains a valid authentication token
const token = req.headers.authorization;
if (!token || !isValidToken(token)) {
res.status(401).send('Unauthorized');
return;
}
next();
}
private isValidToken(token: string): boolean {
// Implement token validation logic here
// ...
}
}
Rate Limiting: Middleware can be used to limit the number of requests from a particular IP address or user.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as rateLimit from 'express-rate-limit';
@Injectable()
export class RateLimitMiddleware implements NestMiddleware {
private readonly limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // Max requests per minute
});
use(req: Request, res: Response, next: NextFunction) {
this.limiter(req, res, next);
}
}
Pipes in NestJS are used to validate and transform data before it reaches the controller or after it has left the controller but before it is sent to the client. Pipes can be used to sanitize and validate incoming data, or transform the data before it is returned to the client.
Some use cases for pipes include:
Validation: Pipes can be used to validate incoming data against predefined rules.
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
}
Transformation: Pipes can be used to transform incoming data to the desired format before it is processed.
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.userService.findOne(id);
}
}
Sanitization: Pipes can be used to sanitize incoming data by removing potentially harmful characters or data.
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseBoolPipe implements PipeTransform<string, boolean> {
transform(value: string): boolean {
if (value.toLowerCase() === 'true') {
return true;
} else if (value.toLowerCase() === 'false') {
return false;
} else {
throw new BadRequestException('Invalid boolean value');
}
}
}
Interceptors in NestJS are used to intercept incoming requests and outgoing responses at various stages of the request lifecycle. Interceptors can be used to manipulate the request or response, add metadata to the request or response, or perform some other action.
Some use cases for interceptors include:
Error Handling: Interceptors can be used to catch errors and return a formatted error message to the client.
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, BadGatewayException } from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class ErrorInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError(error => {
if (error.response) {
return throwError(error.response);
}
throw new BadGatewayException('Something went wrong');
}),
);
}
}
Logging: Interceptors can be used to log request and response data for debugging and auditing purposes.
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggerInterceptor implements NestInterceptor {
private readonly logger = new Logger(LoggerInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const method = request.method;
const url = request.url;
const now = Date.now();
return next
.handle()
.pipe(
tap(() => {
this.logger.log(`${method} ${url} ${Date.now() - now}ms`);
}),
);
}
}
Caching: Interceptors can be used to cache responses for future requests.
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
private readonly cache = new Map();
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const key = request.url;
const cachedResponse = this.cache.get(key);
if (cachedResponse) {
return of(cachedResponse);
}
return next.handle().pipe(
tap(response => {
this.cache.set(key, response);
}),
);
}
}
In summary, middleware is best used for intercepting incoming HTTP requests and outgoing responses, pipes are best used for validating and transforming data, and interceptors are best used for intercepting requests and responses at various stages of the request lifecycle