노현진's Blog

NestJS 기초 사용 방법

NestJS 기초 사용 방법에 대해 설명하는 페이지입니다.

Posted
Preview Image
By HyunJinNo

Tags

TypeScript, NestJS

Environment

Node.js v20.11.1

@nestjs/cli v10.3.2

reflect-metadata v0.2.2

1. 개요

이번 글에서는 NestJS 기초 사용 방법에 대해 설명하도록 하겠습니다.

2. Step 1 - NestJS 애플리케이션 생성

다음 명령어를 입력하여 @nestjs.cli를 사용하여 NestJS 애플리케이션을 생성합니다.

bash
1npx @nestjs/cli new [프로젝트 이름]

명령어를 입력하면 어떤 패키지 매니저를 사용할 것인지를 결정해야 합니다. 저의 경우 npm을 선택하였습니다.

bash
1? Which package manager would you ❤️ to use? npm

3. Step 2 - eslintrc 설정 파일 수정

자신이 사용하는 운영체제에 따라 다음과 같은 오류가 발생할 수 있습니다.

ESLint 오류

이 오류가 발생하는 경우 .eslintrc.js 파일을 열고 다음과 같이 endOfLine에 내용을 추가하거나 변경하면 됩니다.

javascript
1rules: {
2
3  /* ... */
4
5  'prettier/prettier': [
6    'error',
7    {
8      endOfLine: 'auto',
9    },
10  ],
11},

4. Step 3 - Folder Structure / Architecture 확인하기

nest-cli로 생성한 프로젝트 구조는 다음과 같습니다.

plaintext
1├── src
2|   ├── app.controller.spec.ts  // 컨트롤러 테스트 코드
3|   ├── app.controller.ts       // 컨트롤러
4|   ├── app.module.ts           // 모듈
5|   ├── app.service.ts          // 서비스
6|   └── main.ts                 // 서비스 메인 파일
7├── .gitignore                  // git 버전 관리에서 제외할 목록 지정
8├── .prettierrc                 // 코드 포매팅 관련 설정 파일
9├── nest-cli.json               // nest-cli 설정
10└── (...)

4.1. NestJS의 Naming 규칙

NestJS의 Naming Convention은 다음과 같습니다.

  • 파일명은 .으로 연결하며, 모듈이 둘 이상의 단어로 구성되어 있으면 대시로 연결합니다.
    plaintext
    1// <모듈명>.<컴포넌트명>.ts
    2blog.controller.ts
    3my-first.controller.ts
  • 클래스명은 Pascal Case로 표기합니다.
    plaintext
    1// <모듈명><컴포넌트명>
    2BlogController
  • 같은 디렉토리에 있는 클래스는 index.ts를 통해서 임포트하는 것이 권장됩니다.
  • 인터페이스를 사용해서 타입을 정의하고 구체적인 내용을 클래스를 만들어 인터페이스를 상속하는 방식으로 작성합니다.

4.2. main.ts

main.ts 파일은 NestJS 서버의 시작점이 되는 파일입니다. NestJS에서는 진입점을 bootstrap()으로 하는 것이 관례입니다.

typescript
1import { NestFactory } from "@nestjs/core";
2import { AppModule } from "./app.module";
3
4// NestJS를 실행시키는 함수
5// NestJS에서는 진입점을 bootstrap()으로 이름 짓는 것이 관례이다.
6async function bootstrap() {
7  // NestFactory를 사용해서 NestApplication 객체 생성
8  const app = await NestFactory.create(AppModule);
9
10  // 3000번 포트로 서버 기동
11  await app.listen(3000);
12}
13
14bootstrap();

4.3. 모듈명.controller.ts

컨트롤러는 유저가 보낸 HTTP 요청을 어떤 코드에서 처리할지 결정하는 역할을 합니다.

typescript
1import { Controller, Get } from "@nestjs/common";
2import { AppService } from "./app.service";
3
4@Controller() // 컨트롤러 데코레이터
5export class AppController {
6  // 외부에서 사용하므로 export를 붙임.
7
8  constructor(private readonly appService: AppService) {}
9
10  @Get() // GET 요청 처리 데코레이터
11  getHello(): string {
12    return this.appService.getHello();
13  }
14}

4.4. 모듈명.service.ts

서비스는 비즈니스 로직을 담는 파일입니다.

typescript
1import { Injectable } from "@nestjs/common";
2
3@Injectable()
4export class AppService {
5  getHello(): string {
6    return "Hello World! 안녕하세요!";
7  }
8}

4.5. 모듈명.module.ts

모듈은 수평적으로 흩어진 Provider와 Controller들을 논리적인 기능이나 도메인에 따라 하나로 묶어주는 역할을 하며, 재사용성을 높여줍니다.

typescript
1import { Module } from "@nestjs/common";
2import { AppController } from "./app.controller";
3import { AppService } from "./app.service";
4import { BlogModule } from "./blog/blog.module";
5
6// 모듈 데코레이터
7@Module({
8  imports: [],
9  controllers: [AppController],
10  providers: [AppService],
11})
12export class AppModule {}

5. Step 4 - Blog API 만들기

데이터베이스를 사용하지 않는 간단한 Blog API를 만들어 보겠습니다. 먼저 다음과 같이 모듈 단위로 애플리케이션을 구성합니다.

plaintext
1├── src
2|   ├── app.controller.spec.ts
3|   ├── app.controller.ts
4|   ├── app.module.ts
5|   ├── app.service.ts
6|   ├── main.ts
7|   └── modules
8|       └── blog
9|           ├── blog.controller.ts
10|           ├── blog.module.ts
11|           ├── blog.service.ts
12|           └── dtos
13|               └── blog.dto.ts
14└── (...)

modules 디렉토리에 도메인별 모듈을 저장합니다. 각 모듈은 해당 기능과 관련된 컨트롤러, 서비스, DTO 등을 포함합니다.

5.1. blog.controller.ts

typescript
1import {
2  Body,
3  Controller,
4  Delete,
5  Get,
6  Param,
7  Post,
8  Put,
9} from "@nestjs/common";
10import { BlogService } from "./blog.service";
11import { PostDto } from "./dtos/blog.dto";
12
13@Controller("blog") // 클래스에 붙이는 Controller 데코레이터
14export class BlogController {
15  constructor(private readonly blogService: BlogService) {}
16
17  @Get() // GET 요청 처리하기
18  getAllPost() {
19    console.log("모든 게시글 가져오기");
20    return this.blogService.getAllPosts();
21  }
22
23  @Post() // POST 요청 처리하기
24  createPost(@Body() postDto: PostDto) {
25    // HTTP 요청의 body 내용을 post에 할당
26    console.log("게시글 작성");
27    this.blogService.createPost(postDto);
28    return "success";
29  }
30
31  @Get("/:id") // GET 요청에 URL 매개변수에 id가 있는 요청 처리
32  getPost(@Param("id") id: string) {
33    console.log("게시글 하나 가져오기");
34    return this.blogService.getPost(id);
35  }
36
37  @Delete("/:id") // DELETE 방식에 URL 매개변수로 id가 있는 요청 처리
38  deletePost(@Param("id") id: string) {
39    console.log("게시글 삭제");
40    this.blogService.delete(id);
41    return "success";
42  }
43
44  @Put("/:id") // PUT 방식에 URL 매개변수로 전달된 id가 있는 요청 처리
45  updatePost(@Param("id") id: string, @Body() postDto: PostDto) {
46    console.log("게시글 업데이트", id, postDto);
47    return this.blogService.updatePost(id, postDto);
48  }
49}

위의 코드에서 Get, Post, Delete, Put 등의 데코레이터는 모두 함수에 붙이는 것으로, HTTP 요청 방식에 따라 해당 데코레이터가 붙은 함수를 실행합니다. @Body는 함수의 body로 오는 값을 매개변수에 할당하며, @Param은 URL param의 값을 함수 매개변수에 할당합니다.

5.2. blog.service.ts

typescript
1import { Injectable } from "@nestjs/common";
2import { PostDto } from "./dtos/blog.dto";
3
4@Injectable()
5export class BlogService {
6  posts: PostDto[] = []; // 게시글 배열 선언
7
8  // 모든 게시글 가져오기
9  getAllPosts() {
10    return this.posts;
11  }
12
13  // 게시글 작성
14  createPost(postDto: PostDto) {
15    const id = this.posts.length + 1;
16    this.posts.push({
17      id: id.toString(),
18      ...postDto,
19      createdAt: new Date(),
20    });
21  }
22
23  // 게시글 하나 가져오기
24  getPost(id: string) {
25    const post = this.posts.find((post) => post.id === id);
26    console.log(post);
27    return post;
28  }
29
30  // 게시글 삭제
31  delete(id: string) {
32    const filteredPosts = this.posts.filter((post) => post.id !== id);
33    this.posts = [...filteredPosts];
34  }
35
36  // 게시글 업데이트
37  updatePost(id: string, postDto: PostDto) {
38    const updateIdx = this.posts.findIndex((post) => post.id === id);
39    const updatePost = { id, ...postDto, updatedAt: new Date() };
40    this.posts[updateIdx] = updatePost;
41    return updatePost;
42  }
43}

Blog API 로직을 위와 같이 service 파일에 작성합니다.

5.3. blog.module.ts

typescript
1import { Module } from "@nestjs/common";
2import { BlogController } from "./blog.controller";
3import { BlogService } from "./blog.service";
4
5@Module({
6  imports: [],
7  controllers: [BlogController],
8  providers: [BlogService],
9  exports: [],
10})
11export class BlogModule {}

위와 같이 blog 컨트롤러와 서비스를 모듈 파일에 선언합니다.

5.4. blog.dto.ts

typescript
1// 게시글의 타입을 인터페이스로 정의
2export interface PostDto {
3  id: string;
4  title: string;
5  content: string;
6  name: string;
7  createdAt: Date;
8  updatedAt?: Date; // 수정 일시는 필수가 아님.
9}

Dtodata transfer object의 약자입니다. 주로 데이터 전송을 위한 객체로, 애플리케이션 계층 간에 데이터를 주고받을 때 사용됩니다. 타입스크립트에서는 데이터만 가지고 있는 타입을 선언할 때 클래스보다는 인터페이스를 많이 사용합니다.

5.5. app.module.ts

작성한 Blog 모듈을 사용하기 위해 다음과 같이 app 디렉토리의 최상위 모듈 파일에 모듈을 import 합니다.

typescript
1import { Module } from "@nestjs/common";
2import { AppController } from "./app.controller";
3import { AppService } from "./app.service";
4import { BlogModule } from "./modules/blog/blog.module";
5
6// 모듈 데코레이터
7@Module({
8  imports: [BlogModule],
9  controllers: [AppController],
10  providers: [AppService],
11})
12export class AppModule {}

6. Step 5 - Postman으로 테스트하기

6.1. 서버 실행하기

Postman을 사용하여 위의 API가 잘 작동하는지 확인하도록 하겠습니다. 먼저 다음 명령어를 입력하여 서버를 시작합니다.

bash
1npm run start

서버를 시작하는 방법은 위 방법 외에도 다음과 같은 방법이 있습니다. 이에 대해선 package.json 파일을 참고하시길 바랍니다.

bash
1npm run start      // 서버 시작
2npm run start:dev  // development 모드로 실행할 때 사용
3npm run start:prod // production 모드로 실행할 때 사용

6.2. 게시글 작성

게시글 작성 예시

게시글 작성 예시

6.3. 게시글 조회

게시글 조회 예시

© HyunJinNo. Some rights reserved.