본문 바로가기

[NestJS]

[NestJS] - 2편 (설치~인터페이스)

반응형

NestJS - 2편 (설치~인터페이스)

설치

npm i -g @nestjs/cliNestJS generator 설치
nest new project-nameNestJS generator로 새 프로젝트 생성
여기서 폴더구조 쫌 살펴보면 @Controller가 보인다. 엥? 내가 생각하는 그 스프링 어노테이션 컨트롤러가 맞나? 정답: 맞다..
npm i필요한 패키지 설치
npm start

컨트롤러

MVC(Model View Controller)패턴의 컨트롤러가 맞다.
Request, Response를 처리한다.
코드로 이해해보자
nest g controller [name]해보면 띠용? 컨트롤러가 알아서 생성되고 메인 모듈인 app.module.ts에도 알아서 컨트롤러가 등록된다.
컨트롤러 뿐만이 아니다.
nest -h로 생성 가능한 명령어들을 볼 수 있고,
nest g resource [name]로 한번에 CRUD 샘플(module, controller, service, entity, dto, 심지어 테스트 코드까지)을 빠르게 만들 수 있다.
Java Spring 개발해오면서 매번 폴더 새로 만들어주고 생성했던 나에게 충격이었다. 아니 왜케 편해??
스프링에도 있었을거같은데 매번 하나하나 새로 만들던 내가 멍청하게 느껴졌다..

라우팅

우선 코드를 한 번 살펴보자
app.contorller.ts파일
스프링이랑 완전 똑같다...
@Controller 이 어노테이션 선언해서 이게 컨트롤러임을 알게해주듯 여기서도 똑같다. 여기서는 어노테이션 대신 데코레이터라고 한다.
어노테이션 선언 후 ()안에 '/url'을 적어 해당 url로 오는 요청이 처리 가능하다.
위에 어노테이션이 선언되어있다면 아래 함수명은 어떤걸로 하든 무방하다.
(일반적으로 사내 네이밍 규칙을 따름..)
url 선언시 @Get('he*lo')등으로 *,?,+,()등... 정규표현식의 와일드카드도 작동한다. helo , hello, he__lo 등으로 호출이 가능하다.

Request Object

클라이언트로부터 온 데이터(객체로 Nest가 변환해줌)를 다루는 방법 @Req()

  @Get()
  getHello(@Req() req: Request): string {
    console.log(req);
    return this.appService.getHello();
  }

하지만 실제로 API 개발시 직접 객체를 다루는 일은 드물다.
@Query(), @Param(key?: string) , @Body 등의 데코레이터를 이용해 요청에 포함된 쿼리 파라미터, 패스 파라미터, 본문내용을 쉽게 받을 수 있도록 해준다.

Response

자 이번에 앞에서 맛만본 nest g resource [name]를 실제로 써보자
nest g resource Users실행하고 서버를 실행해보자. npm start

[Nest] 9392  - 2022. 04. 18. 오후 4:39:17     LOG [RoutesResolver] UsersController {/users}: +0ms
[Nest] 9392  - 2022. 04. 18. 오후 4:39:17     LOG [RouterExplorer] Mapped {/users, POST} route +2ms
[Nest] 9392  - 2022. 04. 18. 오후 4:39:17     LOG [RouterExplorer] Mapped {/users, GET} route +0ms
[Nest] 9392  - 2022. 04. 18. 오후 4:39:17     LOG [RouterExplorer] Mapped {/users/:id, GET} route +1ms
[Nest] 9392  - 2022. 04. 18. 오후 4:39:17     LOG [RouterExplorer] Mapped {/users/:id, PATCH} route +1ms
[Nest] 9392  - 2022. 04. 18. 오후 4:39:17     LOG [RouterExplorer] Mapped {/users/:id, DELETE} route +1ms
[Nest] 9392  - 2022. 04. 18. 오후 4:39:17     LOG [NestApplication] Nest application successfully started +3ms

이렇게 터미널에 현재 어떤 라우팅으로 요청 받을 수 있는지 확인이 가능하다.

나는 API Tester로 VSCode의 Extension인 REST Client를 주로 이용하는데 원하는 위치 아무곳에나 파일 생성하고 확장자를 .http로 적으면 이용 가능하다.
users.http로 생성하고 맞춰서 작성해보았다.

@local=http://localhost:3000
@users=users

### POST /users
POST {{local}}/{{users}} HTTP/1.1

### GET /users
GET {{local}}/{{users}} HTTP/1.1

### GET /users/1
GET {{local}}/{{users}}/1 HTTP/1.1

### PATCH /users/1
PATCH {{local}}/{{users}}/1 HTTP/1.1

### DELETE /users/1
DELETE {{local}}/{{users}}/1 HTTP/1.1

각각 실행해보면 POST의 성공 응답이 201, 나머지가 200으로 오는게 보이는데,
다른 숫자로 응답하게 수정하고 싶다면 해당 컨트롤러 부분에가서
users.controller.ts파일

import { HttpCode } from '@nestjs/common';

@HttpCode(200)
@Post()

이렇게 데코레이터 선언으로 수정이 가능하다.
요청을 처리 도중 예외를 던저야 한다면 어떻게 해야할까?
users.controller.ts파일

  @Get(':id')
  findOne(@Param('id') id: string) {
    if (+id < 1) {
      throw new BadRequestException('id는 0보다 커야 합니다.');
    }
    return this.usersService.findOne(+id);
  }

이렇게 id가 1보다 작은 값이 전달될 경우 400 에러를 던질 수 있다.
Tip.Nest는 string, number, boolean 등 자바스크립트 원시 타입을 리턴시 직렬화 없이 바로 보내지만, 객체를 리턴한다면 직렬화를 통해 JSON 으로 자동 변환해서 보내준다!

Header 헤더

Nest는 응답시 기본 헤더를 자동으로 구성해서 던져준다.
나만의 헤더를 추가하고 싶다면 @Header데코레이터를 이용해 쉽게 추가가 가능하다.

import { Header } from '@nestjs/common';

@Header('Custom', 'Test Header')
@Get(':id')
findOneWithHeader(@Param('id') id: string) {
  return this.usersService.findOne(+id);
}

Redirection 리다이렉션

클라이언트의 요청을 처리 후 클라이언트를 다른 페이지로 이동시킬 때!
@Redirect데코레이터를 이용해 쉽게 구현이 가능하다.

import { Redirect } from '@nestjs/common';

@Redirect('https://nestjs.com', 301)
@Get(':id')
findOne(@Param('id') id: string) {
  return this.usersService.findOne(+id);
}

처리 결과에 따라 동적으로 리다이렉트시 객체에 담아 리턴해도된다.

{
  "url": string,
  "statusCode": number
}

쿼리 파라미터로 리다이렉트시
ex)버전 숫자를 입력 받아 해당 페이지로 이동시

@Get('redirect/docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}

라우트 파라미터

URL 파라미터를 받을 시 아래와 같은 방법으로 받는게 좋다.

@Delete(':userId/memo/:memoId')
deleteUserMemo(
  @Param('userId') userId: string,
  @Param('memoId') memoId: string,
) {
  return `userId: ${userId}, memoId: ${memoId}`;
}

Sub-Domain Routing 하위 도메인 라우팅

http://localhost, http://api.localhost로 각각 들어온 요청을 서로 다르게 처리하고, 하위 도메인에서 처리하지 못하는 요청은 원래 도메인에서 처리하는 방법!
우선 컨트롤러를 생성해보자
nest g co Api
우리는 ApiController가 먼저 처리될 수 있도록할 것이다.
app.module.ts파일에서 AppControllerApiController의 순서를 바꿔주자.
api.controller.ts파일로가서

@Controller({ host: 'api.localhost' })
export class ApiController {
  @Get()
  index(): string {
    return 'Hello, API';
  }
}

와 같이 수정을하면 해당 url로 들어온 모든 요청들은 저기로 빠지게된다.

로컬 curl 테스트 오류는
Linux : /etc/hosts 에서
Windows : C:\Windows\System32\drivers\etc\hosts 에서

...
127.0.0.1 api.localhost
127.0.0.1 v1.api.localhost

로 수정하면된다.

앞에서 배운 url 파라미터를 @Param 데코레이터로 받는 것처럼
여기서도 @HostParam 데코레이터를 이용해 서브 도메인을 변수로 받을 수 있다.
ex)버전별로 API 도메인 분리

@Controller({ host: ':version.api.localhost' })
export class ApiController {
  @Get()
  index(@HostParam('version') version: string): string {
    return `Hello, API ${version}`;
  }
}

Payload 다루기

POST, PUT, PATCH 요청은 처리에 필요한 데이터를 함께 보낸다.
NestJS에서는 이걸 스프링에서처럼 DTO에 정의해서 쉽게 다룰 수 있다.
(잠깐! DTO & VO 차이는 VO에서는 getter 기능만 존재!)
ex) 회원가입시 이름과 이메일을 받는다고 가정.
create-user.dto.ts 파일

export class CreateUserDto {
  name: string;
  email: string;
}

users.controller.ts 파일

@Post()
create(@Body() createUserDto: CreateUserDto) {
  const { name, email } = createUserDto;

  return `유저를 생성했습니다. 이름: ${name}, 이메일: ${email}`;
}

GET 방식으로 url 파라미터를 통해 유저 목록을 가져오는 경우
GET /users?offset=0&limit=10처럼 페이징 옵션을 받을 수 있는데
이럴 경우 GET용 DTO를 따로 생성하는 것도 가능하다.
get-user.dto.ts 파일 생성 후

export class GetUsersDto {
  offset: number;
  limit: number;
}

참조 : NestJS로 배우는 백엔드 프로그래밍

반응형