출시·고도화 중
NestJS · Express 안내서 · 3/6
Express의 중심은 미들웨어와 라우팅이고, NestJS의 중심은 모듈 · 컨트롤러 · 프로바이더와 의존성 주입입니다. 이 장에서는 두 프레임워크의 핵심을 차례로 보고, Nest가 요청을 처리하는 파이프 · 가드 · 인터셉터와 데코레이터까지 살펴봅니다.
Express에서 요청은 등록된 순서대로 미들웨어 함수를 거칩니다. 미들웨어는 (req, res, next)를 받아 응답을 끝내거나 next()로 다음 함수에 넘깁니다. express.Router()로 경로를 묶고, 인자가 네 개인 함수는 오류 처리 미들웨어로 쓰입니다.
const express = require("express");
const app = express();
// 모든 요청에 적용되는 미들웨어
app.use(express.json());
app.use((req, res, next) => {
console.log(req.method, req.url);
next();
});
// 경로를 묶는 라우터
const users = express.Router();
users.get("/:id", (req, res) => {
res.json({ id: req.params.id });
});
users.post("/", (req, res) => {
res.status(201).json(req.body);
});
app.use("/users", users);
// 오류 처리 미들웨어: 인자가 네 개
app.use((err, req, res, next) => {
res.status(500).json({ message: err.message });
});next(err)를 호출하면 오류 처리 미들웨어로 바로 건너뜁니다. 최신 Express는 async 핸들러가 던진 오류도 오류 처리 미들웨어로 넘겨 줍니다.
모듈은 @Module() 데코레이터가 붙은 클래스로, 관련된 컨트롤러와 프로바이더를 한 단위로 묶습니다. imports에는 이 모듈이 쓰는 다른 모듈을, exports에는 다른 모듈에 내줄 프로바이더를 적습니다. 루트 모듈에서 시작해 모듈들이 이어진 그래프가 애플리케이션 전체가 됩니다.
컨트롤러는 HTTP 요청을 받아 응답을 돌려줍니다. @Controller("cats")가 경로 앞부분을, @Get() · @Post() 같은 데코레이터가 메서드와 나머지 경로를 정합니다. 실제 일은 @Injectable()이 붙은 프로바이더(대개 서비스)가 맡고, 컨트롤러는 생성자에서 그것을 받습니다. 이것이 의존성 주입입니다. Nest의 IoC 컨테이너가 인스턴스를 만들어 넣어 주며, 기본 범위에서는 애플리케이션 전체에서 한 인스턴스를 공유합니다.
import { Body, Controller, Get, Injectable, NotFoundException, Param, ParseIntPipe, Post } from "@nestjs/common";
@Injectable()
export class CatsService {
private readonly cats = [{ id: 1, name: "Nabi" }];
findOne(id: number) {
const cat = this.cats.find((c) => c.id === id);
if (!cat) throw new NotFoundException(`Cat ${id} not found`);
return cat;
}
create(name: string) {
const cat = { id: this.cats.length + 1, name };
this.cats.push(cat);
return cat;
}
}
@Controller("cats")
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
@Post()
create(@Body("name") name: string) {
return this.catsService.create(name);
}
}NotFoundException 같은 내장 예외를 던지면 Nest가 알맞은 상태 코드(여기서는 404)와 JSON 본문으로 응답합니다. 클래스 대신 값이나 팩터리를 주입하고 싶을 때는 { provide: "CONFIG", useValue: {...} }, useFactory, useClass 같은 사용자 정의 프로바이더를 씁니다.
Nest는 요청 하나를 다음 순서로 처리합니다. 각 단계는 전역, 컨트롤러, 메서드 단위로 붙일 수 있습니다.
NestMiddleware 클래스)파이프는 앞 예제의 ParseIntPipe처럼 입력을 바꾸거나 검증하고, 실패하면 400 응답을 보냅니다. 가드는 CanActivate를 구현해 true면 통과, false면 403 응답을 보냅니다. 인터셉터는 핸들러 앞뒤를 감싸며, RxJS 스트림으로 결과를 다룹니다.
import { CallHandler, CanActivate, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Observable, tap } from "rxjs";
@Injectable()
export class ApiKeyGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
return request.headers["x-api-key"] === process.env.API_KEY;
}
}
@Injectable()
export class TimingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const started = Date.now();
return next.handle().pipe(tap(() => console.log(`${Date.now() - started}ms`)));
}
}붙일 때는 @UseGuards(ApiKeyGuard), @UseInterceptors(TimingInterceptor)를 컨트롤러나 메서드에 달거나, main.ts에서 app.useGlobalGuards() · app.useGlobalInterceptors()로 전역에 적용합니다.
Nest의 데코레이터는 클래스와 메서드에 메타데이터를 붙이는 수단입니다. 직접 만든 데코레이터로 역할 정보를 달고, 가드에서 Reflector로 읽을 수 있습니다.
import { CanActivate, ExecutionContext, Injectable, SetMetadata } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
export const Roles = (...roles: string[]) => SetMetadata("roles", roles);
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.getAllAndOverride<string[]>("roles", [context.getHandler(), context.getClass()]);
if (!roles) return true;
const { user } = context.switchToHttp().getRequest();
return roles.some((role) => user?.roles?.includes(role));
}
}이제 핸들러에 @Roles("admin")과 @UseGuards(RolesGuard)를 달면 관리자만 접근할 수 있습니다. 여기서 user는 앞선 인증 단계(예: 인증 가드)가 요청에 넣어 둔 값이라고 가정합니다.
Reflector로 메타데이터를 달고 읽어 권한 검사 같은 공통 기능을 선언적으로 만듭니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.