Lsiron

Nest.js 에서 API 만들기 (get, post, patch, delete) 본문

백엔드/Nest.js

Nest.js 에서 API 만들기 (get, post, patch, delete)

Lsiron 2024. 8. 22. 14:02

Nest.js를 사용하여 간단한 CRUD API를 만드는 방법을 다뤄보겠다.

 

먼저 app.controller.ts 파일로 가보자.

 

app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

 

AppController는 Node.js의 Router와 같은 역할을 한다.

 

즉, Get 데코레이터는 express의 get Router 를 의미하며, 메소드 데코레이터 형식으로 쓰인 것.

 

한번 임의로 데코레이터를 만들고 실행해보자.

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  
  @Get('/lsiron')
  getLsiron(): string {
    return "i'm lsiron";
  }
}

 

lsiron 이라는 경로를 만들어주고, i'm lsiron 문자열을 반환하도록 만들어준 뒤, 실행 해 보았다.

 

주의할 점은 데코레이터는 꾸며주는 함수나 클래스 바로 위에 붙어있어야한다.

( 즉 데코레이터와 함수 사이에 빈칸을 두면 안된다. )

 

 

정말 express에서 get Router와 똑같은 역할을 하는 것을 볼 수 있었다.

 

당연히 @Post로 만들어주면 express에서 app.post를 만든 것 처럼 작동을 한다.

( 대신 @nestjs/common 에서 Post 데코레이터를 가져와주어야 한다.

 

controller는 url을 가져오는 역할만 할 뿐이고, 비즈니스 로직은 service에서 담당한다.

 

nest.js에서 하는 방식으로 바꾸어 준다면 아래와 같다.

 

app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  
  @Get('/lsiron')
  getLsiron(): string {
    return this.appService.getLsiron();
  }
}

 

app.service.ts

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

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
  getLsiron(): string {
    return "i'm lsiron"
  }
}

 

이렇게 controller는 url을 가져오고 function을 반환만 해주며, 이 function의 내용은 service에 들어있다.

( 함수의 이름은 굳이 controller와 똑같이 할 필요는 없다. )

 

이제 nest.js를 설치했을 때, 기본으로 들어있는 내용을 지우고 CRUD API를 새로 만들어보자.

 

먼저 controller와 service 파일을 삭제 해 준 뒤 module파일에 import 해준 controller와 service를 지워준다.

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

@Module({
  imports: [],
  controllers: [],
  providers: [],
})
export class AppModule {}

 

위 처럼 src 폴더는 module과 main 파일만 남겨주고 module 파일 내부는 오로지 Module 만을 남겨놓자.

 

새로운 작업을 위해 첫 번째로 만들어 줄 것은 controller이다.

 

허나 우리는 nest.js를 설치하면서 cli를 사용할 수 있게 되었기 때문에 명령어 만으로 controller를 만들 수 있다.

 

아래의 명령어를 입력하여 설치해주자.

$ nest g co "movies"

 

위 명령어에서 g와 co는 각각 generate 와 controller의 약어이다. 그리고 " " 안에 만들고자 하는 controller의 이름을 적어주면 된다.

 

나는 movies controller를 만들것 이기 때문에 이름을 movies라고 적었다.

import { Module } from '@nestjs/common';
import { MoviesController } from './movies/movies.controller';

@Module({
  imports: [],
  controllers: [MoviesController],
  providers: [],
})
export class AppModule {}

 

이러면 자동으로 movies controller를 담은 폴더가 생성됨과 동시에 module 파일에 moviesController가 기입된 것을 볼 수 있다.

 

spec 파일은 테스트파일인데 나중에 다시 생성 해 줄 것이기 때문에 일단은 지워주자.

 

이제 movies.controller,ts  파일로 가서 Get 요청을 날리는 API를 만들어보자.

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

@Controller('movies')
export class MoviesController {
    @Get()
    getAll(){
        return "This will return all movies";
    }
}

 

위와 같이 코드를 만들어준다. 참고로 @Controller('movies')의 movies는 Entry Point 이다.

 

즉, 이 controller는 무조건 /movies 를 거쳐야 한다는 것 이다.

 

예를들어, movies 를 Entry Point로 입력한 controller에 @get 데코레이터의 경로를 '/lsiron' 으로 설정한다면

lsiron 페이지로 접속하기 위해선 url을 localhost:3000/movies/lsiron으로 입력해야한다.

 

한번 실행한 뒤, /movie url로 들어가보자.

 

 

실행이 잘 되는 것을 볼 수 있다.

 

그러면 url 파라미터 방식으로 API를 하나 더 만들어보자.

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

@Controller('movies')
export class MoviesController {
    @Get()
    getAll(){
        return "This will return all movies";
    }

    @Get('/:id')
    getOne(){
        return "This will return one movie"
    }
}

 

url 파라미터 방식으로 만들어주었다.

 

이제 /movies 뒤에 / 를 넣고 아무 숫자를 입력해도 "This will return one movie" 를 보여줄 것이다.

 

 

실행이 잘 되는 것을 볼 수 있다.

 

프로젝트를 진행하다보면 본문에 params로 id값을 받아올때가 많은데 이 경우는 어떻게 해 주어야 할까?

 

express에서는 const id = req.params.id 이런 형식으로 받아왔었다. nest.js에서도 구성요소는 비슷하지만 구조가 아예 다르다.

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

@Controller('movies')
export class MoviesController {
    @Get()
    getAll(){
        return "This will return all movies";
    }

    @Get('/:id')
    getOne(@Param('id') id: string){
        return `This will return one movie with the id ${id}`;
    }
}

 

req와 const문자가 없는 것 빼고는 구성요소가 똑같다.

 

@nestjs/common 에서 @Param 데코레이터를 가져오고 getOne 함수 인자에 모두 넣어준다.

 

@Param 데코레이터의 인자와 @Get 데코레이터의 인자는 이름이 같아야 하지만, 인자 우측에 있는 id는 const id의 id라고 생각하면 된다.

@Get('/:id')
    getOne(@Param('id') movieId: string){
        return `This will return one movie with the id ${movieId}`;
    }

 

이렇게 'movidId' 로 이름을 바꿔줘도 상관없다. 그냥 변수로 보면 된다.

 

이제 한번 실행 해 보자.

 

실행이 잘 되며, url parameter 에서 데이터를 잘 받아오는 것 까지 확인 할 수 있었다.

 

이제까지 배운Get 요청과 Param을 바탕으로 Post요청과 Delete 요청을 날리는 API를 만들어보자.

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

@Controller('movies')
export class MoviesController {
    @Get()
    getAll(){
        return "This will return all movies";
    }

    @Get('/:id')
    getOne(@Param('id') movieId: string){
        return `This will return one movie with the id ${movieId}`;
    }

    @Post()
    create() {
        return 'This will create a movie';
    }

    @Delete('/:id')
    remove(@Param('id') movieId: string) {
        return `This will delete a movie with the id: ${movieId}`;
    }
}

 

Post API test

 

Delete API test

 

모두 정상적으로 실행 되는 것을 알 수 있다.

 

마지막으로 patch 요청을 날리는 API를 만들어보자. 

 

express에서 Update API를 만들 때 모두 Put을 사용했지만, nest.js 에서는 조금 다르다.

 

바로 전체 업데이트와 부분 업데이트로 나뉘는데, 전체 업데이트는 Put을 사용하고 부분 업데이트는 Patch를 사용한다.

 

Put은 모든 리소스를 업데이트 하기 때문에 위 예시코드의 경우 전체 movie를 업데이트 할 때 사용한다.

 

허나 Patch는 일부 리소스를 업데이트 하기 때문에 위 예시코드의 경우 일부 movie를 업데이트 할 때 사용한다.

 

일단 나는 Patch로 만들어보겠다.

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

@Controller('movies')
export class MoviesController {
    @Get()
    getAll(){
        return "This will return all movies";
    }

    @Get('/:id')
    getOne(@Param('id') movieId: string){
        return `This will return one movie with the id ${movieId}`;
    }

    @Post()
    create() {
        return 'This will create a movie';
    }

    @Delete('/:id')
    remove(@Param('id') movieId: string) {
        return `This will delete a movie with the id: ${movieId}`;
    }

    @Patch('/:id')
    patch(@Param('id') movieId: string) {
        return `This will patch a movie with the id: ${movieId}`;
    }
}

 

Patch API Test

 

모두 정상적으로 실행 되는 것을 알 수 있다.

 

여기서 조금만 더 파고들어 보자.

 

만약 Post메서드로 request의 body 부분을 가져오고 싶다면 어떻게 해야할까?

 

위에서 @param으로 삽입했듯이 똑같이 @body로 삽입하면 된다.

    @Post()
    create(@Body() movieData) {
        console.log(movieData);
        return 'This will create a movie';
    }

 

Param을 적용했을 때와 똑같이 const movieData = req.body 와 똑같다.

( Param을 적용했을땐, @Param의 인자로 id를 넣어줬었는데, Body는 req.body 밖에 없으니 인자를 넣어주지 않은 것 )

 

이제 postman으로 JSON 데이터를 API로 한번 보내보자.

 

send를 누르면 명령어 창에 console.log가 제대로 찍힌 것을 확인 할 수 있다.

 

movieData로 req.body 값 전체를 받아왔으니, movieData만 console 로 찍어도 데이터를 전부 다 가져오는 것 이다.

 

이제 movieData를 return 값으로 넣어보자.

    @Post()
    create(@Body() movieData) {
        return movieData;
    }

 

다시 Postman으로 send를 눌러보자.

 

movieData 값이 그대로 return 되는 것을 확인 할 수 있다.

 

Post 메서드를 통해 body를 생성했다면? 우리는 Patch 메서드 또한 손을 봐 주어야한다. => 생성한 값을 수정하는 로직

 

게시판으로 예를 들자면 글을 작성한 유저의 아이디를 Param으로 받아와서 Body 값을 수정 해 주는 것 이다.

    @Patch('/:id')
    patch(@Param('id') movieId: string, @Body() updateData) {
        return {
            updatedMovie: movieId,
            ...updateData,
        };
    }

 

즉, 업데이트 할 movie의 id랑 우리가 보낼 데이터의 object를 return 해 준 것이다.

 

똑같이 Postman으로 가서 JSON 형식으로 데이터를 보내보자.

 

위와 같이 return이 잘 되는것을 확인 할 수 있다.

 

여기서 nest.js의 장점이 있다. 본래 express.js에서 body값을 JSON 형식으로 파싱하고 JSON 데이터를 return 하려면 별도의 설정이 필요했었다.

app.use(express.json());

 

위 설정이 기억 날 것이다. 

 

허나, nest.js에서는 별도의 설정을 안 해줘도 위와 같이 body값을 자동으로 JSON 형식으로 파싱하고 return까지 해준다.

 

추가로 Get 메서드를 사용하여 API를 하나 더 만들어보자.

import { Controller, Get, Post, Delete, Patch, Param, Body } from '@nestjs/common';

@Controller('movies')
export class MoviesController {
    @Get()
    getAll(){
        return "This will return all movies";
    }

    @Get('/search')
    search(){
        return `We are searching for a movie made after: `    
    }

    @Get('/:id')
    getOne(@Param('id') movieId: string){
        return `This will return one movie with the id ${movieId}`;
    }

    @Post()
    create(@Body() movieData) {
        return movieData;
    }

    @Delete('/:id')
    remove(@Param('id') movieId: string) {
        return `This will delete a movie with the id: ${movieId}`;
    }

    @Patch('/:id')
    patch(@Param('id') movieId: string, @Body() updateData) {
        return {
            updatedMovie: movieId,
            ...updateData,
        };
    }
}

 

/search endpoint로 하나 만들어 주었다.

 

여기서 명심해야 할 점은, express.js와 같이, /search 같이 정적인 경로는 :/:id 와 같은 동적인 경로보다 먼저 정의해야 한다. 

 

만약, /search 보다 /:id가 더 위에 있는 경우에는 /search 경로가 들어왔을 때도 /:id로 처리할 것 이다.

 

이제 Query로 받아오는 Query argument를 작성 해 보자.

    @Get('/search')
    search(@Query('year') searchingYear:string) {
        return `We are searching for a movie made after: ${searchingYear}`    
    }

 

Param을 적용했을 때와 똑같이 const searchingYear = req.query.year 와 똑같다.

 

Postman으로 가서 한번 데이터를 보내보자.

 

return 값을 잘 반환 하는 것을 확인 할 수 있다.

 

이렇게 nest.js로 controller를 통해 get, post, patch, delete 요청을 날리는 API를 만들어 보았다.

 

express를 통해 만들때와 비교적 유사한 점이 많기 때문에 TypeScript만 숙지가 잘 되어 있다면 습득하는 데는 문제가 없어보인다.

 

 

참조 : 노마드코더