2020. 12. 13. 18:07ㆍ개발/Node & Javascript
1. 개요
이전 글 malgogi-developer.tistory.com/2에서는 Nest.js 개발 환경 설정 및 개략적인 구조를 살펴 보았다.
이번 글에서는 Nest.js를 통해서 실제로 API를 작성해보려고 한다.
로컬에 Mysql을 Docker로 셋업해서 진행해보도록 하자.
2. Docker 환경 셋업 및 Mysql 띄우기
Docker는 컨테이너 기반의 가상화 환경을 제공한다. Docker를 이용하게 될 경우 쉽게 개발환경 셋팅 또는 리얼 환경을 구축할 수 있게 도와준다.
docs.docker.com/get-docker/ 아래의 사이트에서 로컬에 Docker를 설치해주도록 한다.
그리고 docker-compose도 설치해주도록 한다. docs.docker.com/compose/install/
docker-compose의 경우에는 docker container들을 손쉽게 구성 및 올리거나 내릴 수 있게 도와준다.
만약에 kubernetes 환경을 통해서 구성하고 싶다면 다음 내용은 스킵해도 좋다.
3. Docker 환경 셋업 및 Mysql 띄우기
아래와 같이 docker-compose.yml 파일을 작성해준다.
version: '3.1'
services:
db:
container_name: malgogi-mysql
image: mysql
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_ROOT_PASSWORD: admin
ports:
- 3306:3306
adminer:
image: adminer
restart: always
ports:
- 8080:8080
그리고 다음의 명령어를 통해서 docker container를 띄워준다.
docker-compose up -d;
# status check
docker ps;
이렇게 확인하게 되면 mysql 컨테이너가 띄워진 것을 확인 가능하다.
4. Nest.js connection setup
우선 시작하기전 typeorm 과 mysql package를 설치하도록 한다.
npm install --save @nestjs/typeorm typeorm mysql
그리고 app.module.ts에서 TypeOrm 관련 설정을 추가해주도록 한다.
@Module({
imports: [
TypeOrmModule.forRoot({
timezone: 'Z',
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'admin',
database: 'malgogi_test',
retryAttempts: 3,
autoLoadEntities: true,
synchronize: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
다음은 몇가지 옵션들을 소개한다.
- synchronize: 이는 entity field를 업데이트할 경우 자동으로 db에 반영되는지 여부를 선택할 수 있다. 이는 production에서는 끄고 사용하는게 좋다.
- autoLoadEntities: 코드에 명시된 entity들을 자동으로 스캔해서 사용한다.
- timezone : DB에 있는 timestamp 또는 date type의 값을 casting할 때 쓰인다. 'local' 또는 'Z' 또는 offset form +HH:MM or -HH:MM. (Default: local)을 사용 할 수 있다.
5. Entity 만들기
일반적으로 Entity의 경우 Database의 Row 또는 하나의 Document의 형태로 대응된다. 즉, 보통 객체를 정의하는 단위로 쓰이는데 Value Object와는 차이가 있다.
도메인 주도 설계 (www.yes24.com/Product/Goods/5312881) 에서는 Entity와 Value Object의 차이를 가장 크게 식별성에 의해서 구분하고 있다고 정의하고 있다. 예를 들어서 우편 서비스 시스템에서의 주소는 Entity가 될 수 있다.
하지만 해당 도메인이 Entity 또는 Value Object가 되는 것은 시스템의 정의에 따라서 달라 질 수 있다.
위의 예제에서의 주소는 Entity가 될 수 있지만 카드 회사에서 주소는 단순히 참조 정보로만 쓰일 수 있다. 이에 따라 Value Object가 될 수 있다.
즉, 객체를 정의함에 있어서 Entity와 Value Object가 존재하고 이를 구별하는 것은 객체의 식별성으로 구분할 수 있다. 하지만 이는 어떠한 시스템을 정의하느냐에 따라서 객체가 Entity 또는 Value Object가 될 수 있음을 주의해야 한다.
개념적인 설명은 끝났고, 아래와 같이 Cats.entity.ts 파일을 만들어보자.
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Generated, Index,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity()
export class Cat {
@PrimaryGeneratedColumn()
idx: number;
@Column({ unique: true })
@Generated("uuid")
id: string;
@Column()
name: string;
@Index("age-idx", { unique: false })
@Column()
age: number;
@Column()
breed: string;
@CreateDateColumn({ type: 'timestamp' })
createdAt!: Date;
@UpdateDateColumn({ type: 'timestamp' })
updatedAt!: Date;
@DeleteDateColumn({ type: 'timestamp' })
deletedAt?: Date;
}
- 여기서 date의 경우 timestamp를 명시해주는 이유는 기본적으로 Date type의 column은 datetime의 형태로 저장된다.
- 그리고 현재 CreateDateColumn, UpdateDateColumn이 있는데, 이는 실제 repository와 연계되어 지정하지 않더라도, 생성, 업데이트 시에 자동으로 생성된다.
- DeleteDateColumn의 경우 softDelete할 경우 사용된다. 또한 실제로 find를 하게 될 경우 불러오지 않게된다.
그리고 아래는 CreateDto이다. validator를 통해서 validation도 추가로 체크하도록 하자.
import { IsNotEmpty, IsNumber, IsPositive } from 'class-validator';
export class CreateCatDto {
@IsNotEmpty()
name: string;
@IsNumber()
@IsPositive()
age: number;
@IsNotEmpty()
breed: string;
}
6. Service logic
이번 튜토리얼에서는 단순한 CRUD의 형태만 다루려고 한다.
이를 통해서 Cats.service.ts를 작성해보자.
import { Injectable } from '@nestjs/common';
import { Cat } from './schemas/cat.entity';
import { CreateCatDto } from './dto/create-cat.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class CatsService {
constructor(
@InjectRepository(Cat)
private catsRepository: Repository<Cat>
) {
}
async create(saveDto: CreateCatDto): Promise<Cat> {
const cat = new Cat();
cat.age = saveDto.age;
cat.breed = saveDto.breed;
cat.name = saveDto.name;
return this.catsRepository.save(cat);
}
async findAll(): Promise<Cat[]> {
return this.catsRepository.find();
}
async delete(id: number): Promise<void> {
await this.catsRepository.softDelete(id);
}
}
7. Controller logic
다음은 controller 쪽이다.
import { Body, Controller, Delete, Get, Param, Post, Query } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './schemas/cat.entity';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {
}
@Post()
async create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
return this.catsService.create(createCatDto);
}
@Get()
async getAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
@Delete('/:id')
async delete(@Param('id') id: number): Promise<void> {
return this.catsService.delete(id);
}
}
8. 후기
이번에는 REST API설계를 통해서 개략적으로 어떻게 동작하는지 살펴봤다.
생각보다 정리하는데 시간이 많이 걸려서 간단한 내용만 정의하고,
추가적으로 다음시간에 각각의 내용을 좀 더 깊게 살펴보려고 한다.
9. 출처
- nest.js: nestjs.com
- typeorm: typeorm.io/#/
'개발 > Node & Javascript' 카테고리의 다른 글
Nest.js 탐험기3 - cache를 써보자 (3) | 2021.01.20 |
---|---|
Nest.js 탐험 2 - Filter를 등록해보자. (1) | 2021.01.16 |
Node.js event loop 정리해보기 (2) | 2021.01.03 |
ECMA 2020 Changes (3) | 2020.12.17 |
Nest.js 탐험 1 - 튜토리얼 맛보기 (1) | 2020.11.28 |