본문으로 건너뛰기
김신건의 로그

[NestJS] Controllers

· 수정 · 📖 약 1분 · 407자/단어 #nestjs #controller #http #routing
NestJS Controller, NestJS Controllers, @Controller decorator, @Get @Post @Put @Delete, @Param @Query @Body, NestJS 컨트롤러

정의

NestJS Controller@Controller() 데코레이터가 붙은 클래스로, HTTP 요청을 라우팅하고 응답을 반환합니다. Method 별로 @Get, @Post, @Put, @Patch, @Delete 데코레이터를 붙여 URL + method 조합을 처리합니다.

기본

import { Controller, Get, Post, Body, Param, Query, HttpCode } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll(@Query('page') page: number = 1) {
    return this.usersService.findAll(page);
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(id);
  }

  @Post()
  @HttpCode(201)
  create(@Body() dto: CreateUserDto) {
    return this.usersService.create(dto);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
    return this.usersService.update(id, dto);
  }

  @Delete(':id')
  @HttpCode(204)
  remove(@Param('id') id: string) {
    return this.usersService.remove(id);
  }
}

URL prefix /users, method 별 하위 경로.

Path Parameter

@Get(':id')
find(@Param('id') id: string) { ... }

@Get(':id/posts/:postId')
findPost(
  @Param('id') id: string,
  @Param('postId') postId: string,
) { ... }

// 전체 params 객체
@Get(':id/posts/:postId')
findPost(@Param() params: {id: string; postId: string}) { ... }

Query Parameter

@Get()
list(
  @Query('page') page: number = 1,
  @Query('limit') limit: number = 10,
) { ... }

// 전체 query 객체
@Get()
list(@Query() query: {page?: string; limit?: string}) { ... }

// DTO 로 (검증 포함)
@Get()
list(@Query() query: ListUsersQuery) { ... }

DTO + ValidationPipe 로 자동 변환/검증:

import { IsInt, Min, Max, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';

export class ListUsersQuery {
  @IsInt()
  @Min(1)
  @Type(() => Number)
  @IsOptional()
  page: number = 1;

  @IsInt()
  @Min(1)
  @Max(100)
  @Type(() => Number)
  @IsOptional()
  limit: number = 10;
}

Request Body

@Post()
create(@Body() dto: CreateUserDto) { ... }

// 특정 필드만
@Post()
create(@Body('email') email: string, @Body('name') name: string) { ... }

Headers, Cookies, IP

@Get()
findAll(
  @Headers('authorization') auth: string,
  @Headers() allHeaders: Record<string, string>,
  @Ip() ip: string,
) { ... }

// Cookies (cookie-parser 필요)
@Get()
withCookie(@Req() req: Request) {
  return req.cookies;
}

Custom Decorator

자주 쓰는 파라미터를 decorator 로:

// common/decorators/user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;
    return data ? user?.[data] : user;
  },
);

// 사용
@Get('me')
me(@User() user: UserEntity) { ... }

@Get('me/email')
myEmail(@User('email') email: string) { ... }

Response

기본 (자동 직렬화)

Method return 값이 자동 JSON 직렬화 + 200 응답.

@Get()
findAll() {
  return {users: [...]};   // -> 200 {"users": [...]}
}

// async 도 OK
@Get(':id')
async find(@Param('id') id: string): Promise<User> {
  return await this.usersService.findOne(id);
}

// Observable 도 OK (RxJS)
@Get()
stream(): Observable<Data> {
  return this.dataService.stream();
}

Status Code

@Post()
@HttpCode(201)              // 명시적 code
create(@Body() dto: CreateUserDto) { ... }

@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)   // 204
remove(@Param('id') id: string) { ... }

Headers

@Get()
@Header('Cache-Control', 'none')
@Header('X-Custom', 'value')
findAll() { ... }

Redirect

@Get('legacy')
@Redirect('/new-path', 301)
legacy() { ... }

@Get('dynamic')
dynamic() {
  return {url: '/new-path', statusCode: 301};   // Runtime redirect
}

Raw Response

세밀한 제어 필요 시 @Res():

import { Res } from '@nestjs/common';
import { Response } from 'express';

@Get('download')
download(@Res() res: Response) {
  res.setHeader('Content-Type', 'application/pdf');
  res.sendFile('/path/to/file.pdf');
}

주의: @Res() 를 쓰면 자동 직렬화 못 함. Interceptor 도 우회. Passthrough option 으로 절충:

@Get()
findAll(@Res({passthrough: true}) res: Response) {
  res.setHeader('X-Custom', 'value');
  return {users: [...]};   // 여전히 자동 직렬화
}

Wildcard / Regex

@Get('a*b')          // /aXXb, /axb, ... 모두 매칭
handle() { ... }

@Get(':version(v[0-9]+)/*')   // /v1/anything
versioned(@Param('version') v: string) { ... }

Sub-domain routing

@Controller({ host: 'admin.example.com' })
export class AdminController { ... }

@Controller({ host: ':account.example.com' })
export class TenantController {
  @Get()
  index(@HostParam('account') account: string) { ... }
}

Async, Promise, Observable

세 스타일 모두 지원:

@Get()
sync() {
  return {ok: true};
}

@Get()
async asyncReturn() {
  return await this.service.get();
}

@Get()
observable(): Observable<Data> {
  return this.service.stream();
}

Streaming Response

import { StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';

@Get('file')
file(): StreamableFile {
  const file = createReadStream('/path/to/file.txt');
  return new StreamableFile(file);
}

또는 @Sse() (Server-Sent Events):

import { Sse } from '@nestjs/common';
import { Observable, interval, map } from 'rxjs';

@Sse('events')
events(): Observable<MessageEvent> {
  return interval(1000).pipe(
    map(_ => ({data: {time: new Date()}}))
  );
}

Versioning

// main.ts
app.enableVersioning({
  type: VersioningType.URI,
  defaultVersion: '1',
});
@Controller({path: 'users', version: '1'})
export class UsersV1Controller { ... }

@Controller({path: 'users', version: '2'})
export class UsersV2Controller { ... }

URL: /v1/users, /v2/users. Header, Media Type 방식도 있음.

Route Prefix

// main.ts
app.setGlobalPrefix('api');

모든 route 앞에 /api.

@Controller('users')
class UsersController {
  @Get()   // -> GET /api/users
}

특정 controller 는 예외:

app.setGlobalPrefix('api', {
  exclude: [{path: 'health', method: RequestMethod.GET}]
});

Guard / Interceptor / Pipe / Filter 부착

@Controller('users')
@UseGuards(AuthGuard)                       // controller 전체
export class UsersController {

  @Get()
  @UseInterceptors(CacheInterceptor)        // method 만
  @UsePipes(new ValidationPipe())
  @UseFilters(new HttpExceptionFilter())
  findAll() { ... }
}

Global 도 가능 (main.ts 참조).

함정

[!WARNING> @Res() 사용 시 자동 응답/interceptor 우회. passthrough: true 사용.

CAUTION

Path 경로 순서. /users/:id/users/me 보다 먼저 정의되면 /users/me 가 id=“me” 로 매치. 구체적 route 를 먼저.

WARNING

class-validator metadata 필요. main.tsimport 'reflect-metadata' (또는 tsconfig 에 emitDecoratorMetadata: true).

IMPORTANT

DTO 안의 Optional field. ? 만으로는 부족. @IsOptional() 데코레이터 함께.

CAUTION

@Body() type 은 runtime 검증 X. TypeScript 는 컴파일 후 사라짐. Pipe 로 실제 검증 필수.

관련 위키

이 글의 용어 (11개)
[FastAPI] Routing (Path Operations)fastapi
정의 FastAPI Routing 은 HTTP method + URL 을 Python 함수 (path operation) 에 매핑하는 시스템입니다. Starlette 의 라우팅 …
[Framework] Koa.jsframeworks
정의 Koa.js 는 Express 창시자 TJ Holowaychuk 이 2013년 발표한 Node.js 웹 프레임워크 입니다. Express 의 후계자 성격이며, 미들웨어를 a…
[Framework] NestJSframeworks
정의 NestJS 는 Kamil Myśliwiec 이 2017년 발표한 Node.js 서버측 프레임워크 입니다. TypeScript first, Angular 에서 영감받은 모듈…
[NestJS] Exception Filtersframeworks
정의 NestJS Exception Filter 는 애플리케이션에서 발생한 예외를 catch 하여 클라이언트에게 응답 형식을 통일합니다. 데코레이터로 어떤 예외를 처리할지 지정합…
[NestJS] Guardsframeworks
정의 NestJS Guard 는 특정 요청이 controller method 에 도달하기 전에 실행되어 접근 허용 여부 를 결정합니다. Middleware 이후, Intercep…
[NestJS] Interceptorsframeworks
정의 NestJS Interceptor 는 요청 처리 전/후에 로직을 삽입하는 클래스입니다. AOP (Aspect-Oriented Programming) 패턴의 구현체로, 로깅,…
[NestJS] Middlewareframeworks
정의 NestJS Middleware 는 route handler 실행 이전, Guard 도 실행되기 전에 실행되는 함수/클래스입니다. Express/Fastify 표준 midd…
[NestJS] Modulesframeworks
정의 NestJS Module 은 데코레이터가 붙은 클래스로, 관련된 controller, provider, import/export 를 하나로 묶는 조직 단위입니다. Angul…
[NestJS] Pipes (Validation, Transformation)frameworks
정의 NestJS Pipe 는 controller method 로 들어가는 인자를 변환 (transformation) 하거나 검증 (validation) 하는 클래스입니다. Gu…
[NestJS] Providers & Dependency Injectionframeworks
정의 NestJS Provider 는 데코레이터가 붙은 클래스 (또는 값/factory) 로, DI container 에 등록되어 다른 클래스에 주입됩니다. Service, Re…
[TypeScript] Decoratorstypescript
정의 Decorator 는 class, method, accessor, property, parameter 에 부착되는 함수로 그 대상을 검사/수정할 수 있습니다. TypeScr…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기