지난 포스팅에서 생성만 했던 nest-ad 프로젝트에서 이어서 가겠습니다.
- 리소스 생성
NestJS 에선 cli 로 필요한 리소스를 쉽게 생성할 수 있습니다. 먼저 nest-cli 가 어떤 것들을 지원하는지 살펴보겠습니다.
> nest --help
이전 포스팅에서 package.json 의 scripts 를 간략히 볼 때 나왔던 build, start 등의 커맨드도 보입니다.
프로젝트에서 필요한 리소스를 구성하는데 주로 사용할 커맨드는 아무래도 generate 입니다. alias 인 g 를 쓸 수도 있네요.
그 아래는 생성할 수 있는 컬렉션들을 보여주고 있습니다.
저는 간단한 광고 서비스 서버를 만들고 싶으니 Ads 라는 도메인에 대해 먼저 Controller 부터 생성해보고자 합니다.
> nest g controller Ads
src 하위에 ads 라는 디렉토리가 생성되고 그 내부에 ads controller 와 spec 코드가 같이 생성된 걸 볼 수 있습니다.
메인 app module 에 추가도 알아서 해주는군요. 이런식으로 service, module, dto, entity 등을 하나하나 생성해서 구성해도 되지만
생성할 수 있는 컬렉션 중 Resource 로 만들면 기본적으로 필요한 것들을 전부 자동으로 생성해줍니다.
> nest g resource Ads
아까 Controller 를 생성할 때랑 다르게 Resource 를 생성하면 NestJS 에서 어떤 Transport layer 를 생성할지 물어봅니다.
현재 만들려는건 간단한 광고 서비스 서버니 REST API 로 시작합니다.
Ads 에 대해 CRUD 를 수행하는 entry point 도 생성할거냐고 추가로 묻습니다. 생성해줍니다.
생성에 실패했습니다. 아까 Controller 를 생성하고 그대로뒀기때문에 해당 리소스가 이미 ads 디렉토리를 차지하고 있어 Conflict 이
발생했기 때문입니다. 아까 만들어진 ads 디렉토리를 제거하고, nest-cli 를 통해 다시 생성합니다.
ads 하위에 module, controller, service, entity, dto 등이 전부 생성되었습니다.
아 참고로 저는 아까 처음 만들었던 Controller 관련된걸 제거하면서 app.module.ts 의 controllers 와 providers 에 남아있던 코드까지
전부 제거했습니다. (기존 app.controller.ts, appl.service.ts 포함)
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { AdsService } from './ads.service';
import { CreateAdDto } from './dto/create-ad.dto';
import { UpdateAdDto } from './dto/update-ad.dto';
@Controller('ads')
export class AdsController {
constructor(private readonly adsService: AdsService) {}
@Post()
create(@Body() createAdDto: CreateAdDto) {
return this.adsService.create(createAdDto);
}
@Get()
findAll() {
return this.adsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.adsService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateAdDto: UpdateAdDto) {
return this.adsService.update(+id, updateAdDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.adsService.remove(+id);
}
}
아까 CRUD entry point 도 같이 생성했기 때문에, ads.controller.ts 파일을 보면 위와 같이 생성된 것을 볼 수 있습니다.
Controller 데코레이터를 통해 ads 라는 경로로 라우팅되면서 각 CRUD operation 에 해당하는 GET, POST, PATCH, DELETE 데코레이터가 붙었네요. 이렇게 데코레이터로 표현해주는게 가독성에 상당한 도움이 되는 것 같습니다.
그럼 한번 정상적으로 동작하는지 먼저 체크해보죠.
> npm run start:dev
브라우저를 통해 ads 경로로 GET 접근을 해보면 정상 응답합니다. 여기서부터 하나하나 살을 붙여나가면 될 것 같네요.
살을 붙이기에 앞서 생성된 리소스들에 대해 부가적인 것들을 간단히 살펴보겠습니다.
- 라우팅 패스 와일드 카드(*) 사용
데코레이터 중 @Controller 나 @Get, @Post 등에는 라우팅 패스를 지정할 수 있는데, 이때 와일드 카드도 지정할 수 있습니다.
@Controller('ads')
export class AdsController {
constructor(private readonly adsService: AdsService) {}
...
@Get('/tmp/tm*p')
findAll() {
return this.adsService.findAll();
}
...
}
아까는 findAll 메서드의 @Get 데코레이터에 인자를 넣지 않았기 때문에 localhost:3000/ads 요청에 대해선 findAll 로 들어왔지만,
위처럼 변경하면 localhost:3000/ads 요청에 대해선 404 Not Found 응답을 합니다.
반면 localhost:3000/tmp/tmp 또는 localhost:3000/tmp/tm.p 등과 같은 경로로 정규 표현식의 와일드카드처럼 동작해 요청을
처리할 수 있게 됩니다. 물론 localhost:3000/tmp/tm#p 와 같은 특수 기호에 대해선 동작하지 않습니다.
그냥 이런게 가능하다 정도지 사실 라우팅 패스에 와일드 카드를 쓸 일은 거의 없을 것 같습니다.
- 요청 및 응답 객체
일반적으로 Express 에서 req, res 객체를 다루듯이 NestJS 에서도 해당 객체들을 다룰 수 있습니다.
import { ..., Req, Res } from '@nestjs/common';
@Controller('ads')
export class AdsController {
constructor(private readonly adsService: AdsService) {}
...
@Get()
findAll(@Req() req, @Res() res) {
console.info(req);
const ads = this.adsService.findAll();
return res.status(200).send(ads);
}
...
}
Req, Res 데코레이터는 다른 데코레이터들과 마찬가지로 @nestjs/common 이 제공합니다.
위처럼 작성후 요청을 보내보면, req 객체가 어떻게 구성되어있는지 많은 정보들을 확인할 수 있습니다.
res 객체를 사용한 응답 구성은 Express 에서 흔히 보던 방법입니다. 똑같이 할 수 있다는걸 보였을 뿐 이 객체들을 직접 다룰 일이 당장은 많지 않을 것 같습니다.
res.status 와 같이 res 객체를 사용해 응답 코드를 지정할 수도 있겠지만, NestJS 에선 역시나 이또한 데코레이터로 기능을 제공합니다.
import { ..., HttpCode } from '@nestjs/common';
@Controller('ads')
export class AdsController {
constructor(private readonly adsService: AdsService) {}
...
@HttpCode(202)
@Get()
findAll() {
return this.adsService.findAll();
}
...
}
HttpCode 데코레이터를 사용해 해당 요청에 대한 응답 코드를 바꿀 수 있습니다. 위처럼 정상 응답에 대한 코드를 202 로 바꾸고,
요청을 보내면 아래와 같은 응답을 확인할 수 있습니다.
> curl -I localhost:3000/ads
- 리다이렉션
서버에선 간혹 요청을 처리하고선 클라이언트를 다른 페이지로 이동하고 싶을 때가 있습니다.
여기서 하진 않겠지만, 예를 들면 광고 서버의 경우엔 클릭 이벤트가 발생했을 때 관련 로직들을 처리한 후 본래 해당 컨텐츠의 페이지로
리다이렉트 시키는게 일반적인 동작입니다. 이럴 경우 Express 에선 res 객체의 redirect 메서드를 사용했지만, NestJS 에선
역시나 Redirect 데코레이터를 사용할 수 있습니다.
import { ..., Redirect, Query } from '@nestjs/common';
@Controller('ads')
export class AdsController {
constructor(private readonly adsService: AdsService) {}
...
@Redirect('https://nestjs.com', 302)
@Get('click')
click(@Query('id') id) {
if (id === '100') {
return { url: 'https://docs.nestjs.com/' };
}
}
...
}
간단한 예시 코드입니다. /ads/click 경로로 요청이 들어오면, Redirect 데코레이터를 통해 https://nestjs.com 페이지로 보냅니다.
그런데 저 메서드 안에 로직들은 뭘 하는거냐면, 로직 결과에 따라 동적으로 리다이렉트 페이지를 변경하는 코드입니다.
/ads/click 요청 중 query parameter 로 id 값이 100 으로 들어온 경우엔 보내고자하는 페이지의 주소를 url 에 담은 리터럴 객체를 반환하면 해당 주소로 리다이렉트 되는걸 확인할 수 있습니다.
localhost:3000/ads/click / localhost:3000/ads/click?id=100 두 요청에 대한 리다이렉트를 직접 확인해보시면 될 것 같습니다.
- MicroService
이건 지금 자세히 보려는건 아니고, 아까 nest-cli 를 통한 Resource 생성시 REST API, GraphQL, WebSockets 외에 목록에 있었던Microservice 를 선택했을 땐 어떻게 생성되는지만 확인해봤습니다.
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller()
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@MessagePattern('createUser')
create(@Payload() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@MessagePattern('findAllUsers')
findAll() {
return this.usersService.findAll();
}
@MessagePattern('findOneUser')
findOne(@Payload() id: number) {
return this.usersService.findOne(id);
}
@MessagePattern('updateUser')
update(@Payload() updateUserDto: UpdateUserDto) {
return this.usersService.update(updateUserDto.id, updateUserDto);
}
@MessagePattern('removeUser')
remove(@Payload() id: number) {
return this.usersService.remove(id);
}
}
module, service, entity, dto 등은 동일한데 controller 의 코드가 다른것을 볼 수 있습니다.
REST API 는 CRUD operation 에 대해 GET, POST, PATCH, DELETE method 로 나타내게 되어 있으므로 각 명칭과 동일한 데코레이터로 구성되어있었지만, 일반적인 MSA 구성에선 (꼭 그런건 아니지만) 이벤트 기반 구조를 취하는 경우가 많으므로 MessagePattern 데코레이터로 구성되어있는 것을 볼 수 있습니다.
MessagePattern 데코레이터를 통해 이벤트 핸들러를 등록하여 해당 이벤트가 발생했을 때 로직을 처리하는 방식입니다.
반대로 이벤트를 send 하거나 emit 하는 것도 구현 가능하며, 스트림 완료에 대한 알람도 받을 수 있습니다.
아까 Microservice Resource 를 생성할 때 전송 계층에 non-HTTP 라고 써있었듯이 전송에는 TCP, KAFKA, REDIS, GRPC 등을 사용합니다. MicroService 에 대해선 이정도까지만 보고 나중에 좀 자세히 들여다보도록 하겠습니다.
자동 생성된 dto 나 entity 들은 여기서 더 살을 붙여나가면서 당연히 수정해야하기에 이후 포스팅에서 살펴봅니다.
'Backend > NestJS' 카테고리의 다른 글
NestJS - 대충 서비스 만들어보기 (6) (0) | 2023.04.15 |
---|---|
NestJS - 대충 서비스 만들어보기 (5) (0) | 2023.04.08 |
NestJS - 대충 서비스 만들어보기 (4) (0) | 2023.03.25 |
NestJS - 대충 서비스 만들어보기 (3) (0) | 2023.03.19 |
NestJS - 대충 서비스 만들어보기 (1) (0) | 2023.02.25 |