NestJs 란?

pingu·2023년 4월 18일
0

typescript

목록 보기
2/3
post-thumbnail

NestJS

요즘 안그래도 백엔드 express로 간단하게 서버 만들어보고 싶었는데 기회가 생겨서 Nest를 사용하는 프로젝트를 시작하게 되었다.

지금도 바빠 뒤지겠는데 프로젝트 또 생겼다. 너무 좋다.


NestJS는 왜 탄생하였나?

프론트엔드엔 리액트, 앵귤러, 뷰 등 다양한 프레임워크가 있는 반면에 백엔드를 위한 라이브러리, 도우미 및 도구 등이 많이 존재하지만 Node.js의 높은 자유도로 인해 아키텍처의 구성이 어렵고
효과적이지 못하다.
이를 해결하기 위해 Angular의 아키텍처 사상에서 영감을 받아 만들어졌다.
따라서 앵귤러 개발자는 쉽게 접근, 이해가 가능하다고 한다.

일단 자바스크립트하나로 풀스택 개발이 가능하다는 것 자체가 굉장한 이점이다.


특징

  • Nest는 Nodejs 런타임 위에서 동작하는 TypeScript용 오픈 소스 백엔드 웹 프레임워크이다.

  • 기존의 자바스크립트 프레임워크와는 다르게, '타입스크립트를 지원할 수 있다'가 아니라 처음부터 '반드시 타입스크립트로 사용할 것'을 가정하고 만들어졌다. 물론 js 로도 쓸 수는 있다.

  • Nest는 Express 기반으로 만들어졌기에 Express문법을 전부 포함하며
    프로그레시브 JavaScript를 사용하고 TypeScript 로 구축되어 완벽하게 지원하며 OOP(객체지향 프로그래밍), FP(함수형 프로그래밍) 및 FRP(함수형 리액트 프로그래밍)을 포함한다.

  • 기본적으로 제공하는 라우팅, 보안등의 기능이 많이 탑재되어 있어 편리하다.

  • 리액트마냥 외부모듈을 통한 확장이 가능하다.

  • Java+Spring의 아키텍처 구조와 비슷하다

  • NestJS는 코드를 거시적인 관점에서 Provider, Controller, Module로 분리하며 이런 작은 컴포넌트들끼리의 조합(DI)을 통해 전체 애플리케이션을 완성한다.

  • 코드 자동화가 굉장히 잘 되어있다.
    기존 스프링 등과 같이 파일 구조가 복잡하고 동시에 여러 부분을 변경할 필요가 있는 프레임워크는 매번 빈번히 여러 파일을 수정하기도 불편하고 실수가 발생하기 쉬웠기에 IDE에서 자동화를 지원했지만 nest는 아에 cli를 만들어 터미널에 입력만 하면 CRUD 컨트롤러, 서비스, 유닛 테스트, DTO, 심지어 엔티티 정의까지 자동으로 만들어 준다.
    ㅇㄱㄹㅇ 개사기 진짜 백엔드 몰라도 개발할 수 있는 수준


설치

Nestjs cli 설치

npm i -g @nestjs/cli

프로젝트 생성 ( Create-React-App처럼 코드 한줄로 다 생성해줌 )

nest new {프로젝트명}

또는 설치 + 프로젝트 생성 ( 어디서 많이 보던 거 )

npx -y @nestjs/cli new {프로젝트명}

프로젝트 생성을 하면 이런 파일들을 싸질러 놓는다.

  • dist : typescript 코드를 컴파일해서 빌드한 .js 파일이 저장되는 폴더 (난 왜 없지)
  • node_moduels : package.json에 정의된 패키지 모듈이 설치되는 폴더
  • src : typescript 코드가 저장되는 사용자 소스 폴더
  • test : test 소스가 저장되는 폴더

src 폴더를 들어가면 다음과 같은 파일들을 볼 수 있다.

  • app.controller.spec.ts : test용 소스 - controller를 테스트
  • app.controller.ts : controller 소스 - client의 request을 처리하고, response를 보내기, 라우팅 처리
  • app.module.ts : module 소스 - 모듈을 정의(controller와 service 정의)
    - main.ts에서 지정한 root module
  • app.service.ts : service 소스 - controller가 요청한 비즈니스 로직을 처리
  • main.ts : 프로젝트 시작점(Entry Point), 시작 소스
    - 서버를 스타트하고 포트 설정, CORS 등을 정의

프로젝트 실행

npm start

http://localhost:3000

나중에 로컬에서 테스트할때는 클라이언트랑 충돌되지 않게 포트번호를 바꿔줘야함


헬로월드!
이제 이력서 기술스택에 NestJS 추가하러 가자


Module

  • NestJS는 여러 모듈들의 집합이다
  • NestJS는 모듈 단위로 개발
  • module은 @Module 데코레이터를 사용하여 선언
  • imports는 다른 module을 가져올때 사용
  • controllers는 이 모듈에서 사용하는 컨트롤러의 세트
  • providers는 NestJS의 injector에 의해 인스턴스화 되는 class를 지정
  • NestJS는 하나 이상의 모듈이 반드시 있어야 한다.
  • 기본 모듈은 AppModule
  • 기능별로 모듈을 생성하여 개발
  • 생성된 각 모듈을 Application Module에 imports 해서 사용 ( props 마냥 하위모듈의 하위모듈... )
  • CLI로 모듈을 생성하면 자동으로 Root Module에 import
  • export를 통해 외부에서 module을 사용 가능

main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();
  • main.ts는 Application의 진입점(Entry Point)
  • main.ts에서는 NestFactory를 사용하여 Application 인스턴스를 생성
  • 생성시에는 AppModule을 사용하도록 지정

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
  • AppModule은 main.ts에서 지정한 root module
  • NestJS는 모듈 단위로 개발을 진행
  • module은 @Module 데코레이터를 사용하여 선언
  • imports는 다른 module을 가져올때 사용
  • controllers는 이 모듈에서 사용하는 컨트롤러의 세트
  • providers는 NestJS의 injector에 의해 인스턴스화 되는 class를 지정
  • export를 통해 모듈을 다른 곳에서 사용 가능

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();
  }
}
  • controller는 client의 요청에 대한 라우팅을 처리
  • provider인 AppService에게 비즈니스 처리 요청을 보냄
  • 서비스에서 받은 결과를 client에 응답으로 보냄

app.service.ts

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

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

controller의 요청을 받은 서비스는 비즈니스 로직을 처리한 후 controller에게 return


Generator - 소스 자동 생성기 ( Module, Controller, Service)

Module 자동 생성

nest g module {모듈명}


폴더와 파일 생성됨

UsersModule이 자동으로 app.module.ts에 import 됨

Controller 자동 생성

nest g controller {컨트롤러명}


컨트롤러 파일 두개가 생성되고 모듈에 UsersController가 추가됨

Service 자동 생성

nest g service {서비스명}


서비스 파일 두개 생성되고 모듈에 providers: [UserService] 추가됨


Controller

Request 객체

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Req() request: Request): string {
    console.log(request);
    return 'This action returns all cats';
  }
}
  • Controller는 @Controller() 데코레이터를 사용한다.
  • () 안에 라우팅 path를 지정할 수 있다. ( 현재 '/users' 이다, 없으면 root path '/' )
  • @Get('profile') 데코레이터 안에서도 @Controller와 마찬가지로 routing path를 지정할 수 있다. http://localhost:/3000/cats/profile
  • @Get() 데코레이터 아래 선언된 Method의 이름은 어떤 의미도 부여하지 않는다.
  • findAll(): string 은 response 형식 type이다
  • id가 1일 경우 호출 url은 http://localhost:3000/cats/1
@Get(':id')
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}
@Get(':id')
findOne(@Param('id') id: string): string {
  return `This action returns a #${id} cat`;
}
  • 매개 변수는 @Param() 데코레이터로 받아서 처리
  • params.id로 라우팅 매개변수 값을 추출 가능

비동기 처리

@Get()
async findAll(): Promise<any[]> {
  return [];
}

Promise를 반환해야함

Post Body DTO

Post 라우트 핸들러에서는 @Body() 데코레이터를 사용
이때 DTO 스키마 클래스를 생성해서 데이터를 처리

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

DTO를 Controller에서 사용하려면?

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}

일반적인 API의 구조를 모두 포함한 예제는 다음과 같습니다.

  • findAll : 전체 데이터 목록 조회
  • findOne : 데이터 상세 조회
  • create : 데이터 생성
  • update : 데이터 수정
  • remove : 데이터 삭제
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto';

@Controller('cats')
export class CatsController {
  @Post()
  create(@Body() createCatDto: CreateCatDto) { // 데이터 생성
    return 'This action adds a new cat';
  }

  @Get()
  findAll(@Query() query: ListAllEntities) { // 전체 데이터 목록 조회
    return `This action returns all cats (limit: ${query.limit} items)`;
  }

  @Get(':id')
  findOne(@Param('id') id: string) { // 데이터 상세 조회
    return `This action returns a #${id} cat`;
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) { // 데이터 수정
    return `This action updates a #${id} cat`;
  }

  @Delete(':id')
  remove(@Param('id') id: string) { // 데이터 삭제
    return `This action removes a #${id} cat`;
  }
}


Service ( Provider )

Providers

  • provider는 services, repositories, factories, helpers 등이 있다.
  • provider는 종속성에 의해 Inject(주입)가능
  • provider 객체의 생성 및 연결은 nest runtime 시스템에 위임될 수 있다.
  • 컨트롤러는 HTTP 요청을 처리하고 복잡한 작업은 Provider에게 위임함
  • provider는 module에서 선언하는 일반 javascript class 이다.

Service

Controller에서 사용할 Service이며, 데이터의 조회, 저장, 수정, 삭제를 처리한다.

nest g service cats

@Injectable() 데코레이터는 이 class가 Nest IoC 컨테이너에서 관리하는 class 임을 선언

import { Injectable } from '@nestjs/common';
import { User } from './interface/user.interface';

@Injectable()
export class UsersService {
    private readonly users: User[] = [];

    create(user: User) {
        this.users.push(user);
    }

    findAll(): User[] {
        return this.users;
    }
}

user.interface.ts

export interface User {
    name: string;
    age: number;
    breed: string;
}

이제 UsersController를 다음과 같이 바꿀 수 있습니다.

export interface User {
    name: string;
    age: number;
    breed: string;
}

미들웨어(Middleware)

미들웨어는 라우터 핸들러 이전에 호출되는 함수이다.

위 그림에서와 같이 클라이언트의 요청을 라우터 핸들러가 받기 전에 가로채 다른 작업을 처리할 수 있다.

이를 응용하면 여러가지 공통적으로 처리해야 하는 부분들의 처리를 중복 없이 개발할 수 있다.

모든 코드가 공통으로 실행해야 하는 인증, 로깅등을 처리할 수 있다.

  • 요청과 응답 객체를 변경할 수 있다.
  • 요청의 validation을 체크하여 오류 처리를 할 수 있다.

Nest의 미들웨어 사용법

  • @Injectable 데코레이터를 사용한다.
  • NestMiddleware 인터페이스를 implements 해서 사용한다.
  • Module의 class 내부에 configure를 사용하여 선언하고 이때 NestModule 인터페이스를 implements 한다.

미들웨어 폴더를 하나 만들고 파일을 만든다.
logger.middleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

app.module.ts

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('users');
  }
}

TypeORM

코드에 있는 "객체"와 DB에 있는 "데이터"를 편하게 일치시켜주는 도구이다.
즉, 우리가 만든 "객체"에 맞춰 SQL을 자동 생성해 데이터와 "동기화"시키는 일을 하는 것이 ORM이다.

typeorm 설치

npm install --save @nestjs/typeorm typeorm mysql2

app.module.ts에 TypeORM 모듈을 import

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [],
      synchronize: true,
    }),
    CatsModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

cats/entity/cats.entity.ts 파일 생성

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Cat {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  age: number;

  @Column()
  breed: string;

  @Column({ default: true })
  isActive: boolean;
}

app.module.ts에 cats.entity 추가

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
import { Cat } from './cats/entity/cats.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [Cat],
      synchronize: true,
    }),
    CatsModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

cats.module.ts에서 cats.entity를 사용하기 위해 코드 추가

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { Cat } from './entity/cats.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Cat])],
  exports: [TypeOrmModule],
  controllers: [CatsController],
  providers: [CatsService]
})
export class CatsModule {}
profile
코딩공부 리뷰

0개의 댓글