[NestJS 강의] Slack 클론 코딩[백엔드 with NestJS + TypeORM] 섹션 2

강경서·2025년 9월 1일
post-thumbnail

🎡 API 설계하기

모듈, 컨트롤러 생성

Nest CLI 를 통해 모듈, 컨트롤러, 서비스 프로젝트를 간편하게 생성할 수 있습니다.

$ nest generate module name
$ nest g mo name

$ nest generate controller name
$ next g co name

$ nest generate servies name
$ nest g s name

DTO ( Data Transfer Object )

DTO 는 클라이언트와 서버간에 데이터를 주고받을 때, 데이터의 구조와 타입을 정의하는 객체입니다.

// back/src/users/users.controller.ts

 @Post()
 postUsers(@Body() data: JoinRequestDto) {
    this.usersService.postUsers(data.email, data.nickname, data.password);
 }

// back/src/users/dto/join.request.dto.ts

export class JoinRequestDto {
  public email: string;

  public nickname: string;

  public password: string;
}

이와 같이 interface 가 아닌 class 로 제작하게 되면 빌드 후 JS 에서 사라지지 않아 타입 안정성 뿐만 아니라 런타임 검증( Express validator, joi 라이브러리 사용 )까지 가능합니다.


Controller

controller 제작 시 NestJSreq res 객체 직접 접근은 최소화하고, @Body() @Query() @Param() 같은 데코레이터로 필요한 데이터만 받는 걸 권장합니다.

// back/src/users/users.controller.ts

import { Body, Controller, Get, Post, Req, Res } from '@nestjs/common';
import { JoinRequestDto } from './dto/join.request.dto';
import { UsersService } from './users.service';

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

  @Get()
  getUsers(@Req() req) {
    return req.users;
  }

  @Post('logout')
  logOut(@Req() req, @Res() res) {
    req.logOut();
    res.clearCookie('connect.sid', { httpOnly: true });
    res.send('ok');
  }
}

// back/src/dms/dms.controller.ts

import { Controller, Get, Param, Post, Query } from '@nestjs/common';

@Controller('api/workspaces/:url/dms')
export class DmsController {
  @Get(':id/chats')
  getchat(@Query() query, @Param() param) {
    console.log(query.perPage, query.page, param.id, param.url);
  }
  // 분리해서 사용도 가능
  @Get(':id/chats')
  getchat(@Query('perPage') perPage, @Query('page') page, @Param('id') id, @Param('url') url) {
    console.log(perPage, page, id, url);
  }
}

Swagger

Swagger 는 애플리케이션의 RESTful API 문서를 자동으로 구성하는 특수 도구입니다.
NestJS 는 이러한 Swagger 제작을 위한 모듈을 제공합니다.

npm install --save @nestjs/swagger
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const port = process.env.PORT || 3000;

  const config = new DocumentBuilder()
    .setTitle('Sleact API')
    .setDescription('Sleact 개발을 위한 API 문서입니다.')
    .setVersion('1.0')
    .addCookieAuth('connect.sid')
    .build();
  const documentFactory = () => SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, documentFactory);

  await app.listen(port);
  console.log(`listening on port ${port}`);
}
bootstrap();

NestJSSwagger 을 설정한 후 서버를 실행하면 /api 경로에 Swagger UI를 만들어줍니다.

// User Controller

@ApiTags('USER') // 컨트롤러 간의 구분
@Controller('api/users')
export class UsersController {
  
  @ApiResponse({ status: 200, description: '성공', type: UserDto }) // 응답 설명
  @ApiOperation({ summary: '내 정보 조회' }) //  api 설명
  @Get()
  getUsers(@Req() req) {
    return req.users;
  }
}

// DM Controller

@ApiTags('DM') // 컨트롤러 간의 구분
@Controller('api/workspaces/:url/dms')
export class DmsController {
  
  @ApiParam({ name: 'url', description: '워크스페이스 url', required: true }) // Param 설명
  @ApiParam({ name: 'id', description: '시용자 id', required: true }) // Param 설명
  @ApiQuery({ name: 'perPage', description: '한 번에 가져오는 개수', required: true }) // Query 설명
  @ApiQuery({ name: 'page', description: '불러올 페이지', required: true }) // Query 설명
  @Get(':id/chats')
  getchat(@Query() query, @Param() param) {}
}

// Dto

export class JoinRequestDto {
  @ApiProperty({ example: 'kangkyeongseo@gmail.com', description: '이메일', required: true }) // Property 설명
  public email: string;

  @ApiProperty({ example: '덮밥', description: '닉네임', required: true }) // Property 설명
  public nickname: string;

  @ApiProperty({ example: 'password', description: '비밀번호', required: true }) // Property 설명
  public password: string;
}

위와 같이 @nestjs/swagger 가 제공하는 데코레이터를 이용하여 Swagger 문서를 구성할 수 있습니다.


커스텀 데코레이터 만들기

// back/src/common/decorator/user.decorator.ts

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

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

// back/src/users/users.controller.ts

@Get() // 커스텀 데코레이터사용
getUsers(@User() user) {
  return user;
}

ExecutionContext 를 통해 Wep Socket RPC HTTP 를 하나의 컨텍스트로 관리하고 있습니다.

커스텀 데코레이터를 구성하여 사용하면 코드의 중복을 감소, 코드 수정의 편의 및 컨트롤러의 의존성을 줄여 테스트의 편의를 증진시킬 수 있습니다.


인터셉터 사용하기

인터셉터는 미들웨어와 달리 컨트롤러의 작동 전 뿐만이 아니라 컨트롤로 작동 후에도 작업이 가능합니다.

// back/src/common/interceptors/undefinedToNull.interceptor.ts

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

@Injectable()
export class undefinedToNullInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
    // 컨트롤로 작동 전 부분
    return next.handle().pipe(map((data) => (data === undefined ? null : data))); // 컨트롤로 작동 후 부분 
  }
}
// back/src/users/users.controller.ts

@UseInterceptors(UndefinedToNullInterceptor) // 컨트롤러 전체에 인터셉터 사용
@Controller('api/users')
export class UsersController {
  @UseInterceptors(UndefinedToNullInterceptor) // 특정 컨트롤러에 인터셉터 사용
  @Get()
  getUser() {}
 }

RxJS(Reactive Extensions for JavaScript)는 비동기 프로그래밍을 위한 라이브러리로, Observable 을 사용하여 데이터 스트림을 처리하고 조작할 수 있도록 해줍니다.

Observable : 시간이 지남에 따라 발생하는 데이터(이벤트, Ajax 응답 등)를 감싸는 객체.


📝 후기

NestJS가 추구하는 구조적이고 명확한 구분을 하는 개발 방식을 볼 수 있었습니다.


🧾 Reference

profile
기록하고 배우고 시도하고

0개의 댓글