요즘 안그래도 백엔드 express로 간단하게 서버 만들어보고 싶었는데 기회가 생겨서 Nest를 사용하는 프로젝트를 시작하게 되었다.
지금도 바빠 뒤지겠는데 프로젝트 또 생겼다. 너무 좋다.
프론트엔드엔 리액트, 앵귤러, 뷰 등 다양한 프레임워크가 있는 반면에 백엔드를 위한 라이브러리, 도우미 및 도구 등이 많이 존재하지만 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, 심지어 엔티티 정의까지 자동으로 만들어 준다.
ㅇㄱㄹㅇ 개사기 진짜 백엔드 몰라도 개발할 수 있는 수준
npm i -g @nestjs/cli
nest new {프로젝트명}
또는 설치 + 프로젝트 생성 ( 어디서 많이 보던 거 )
npx -y @nestjs/cli new {프로젝트명}
프로젝트 생성을 하면 이런 파일들을 싸질러 놓는다.
src 폴더를 들어가면 다음과 같은 파일들을 볼 수 있다.
npm start
나중에 로컬에서 테스트할때는 클라이언트랑 충돌되지 않게 포트번호를 바꿔줘야함
헬로월드!
이제 이력서 기술스택에 NestJS 추가하러 가자
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();
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 {}
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();
}
}
app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
controller의 요청을 받은 서비스는 비즈니스 로직을 처리한 후 controller에게 return
nest g module {모듈명}
폴더와 파일 생성됨
UsersModule이 자동으로 app.module.ts에 import 됨
nest g controller {컨트롤러명}
컨트롤러 파일 두개가 생성되고 모듈에 UsersController가 추가됨
nest g service {서비스명}
서비스 파일 두개 생성되고 모듈에 providers: [UserService] 추가됨
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';
}
}
@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`;
}
@Get()
async findAll(): Promise<any[]> {
return [];
}
Promise를 반환해야함
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의 구조를 모두 포함한 예제는 다음과 같습니다.
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`;
}
}
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;
}
미들웨어는 라우터 핸들러 이전에 호출되는 함수이다.
위 그림에서와 같이 클라이언트의 요청을 라우터 핸들러가 받기 전에 가로채 다른 작업을 처리할 수 있다.
이를 응용하면 여러가지 공통적으로 처리해야 하는 부분들의 처리를 중복 없이 개발할 수 있다.
모든 코드가 공통으로 실행해야 하는 인증, 로깅등을 처리할 수 있다.
미들웨어 폴더를 하나 만들고 파일을 만든다.
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');
}
}
코드에 있는 "객체"와 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 {}