app.module
->app.controller
->app.service
cotroller
는 통신(router) 역할과 request, response를 받아서 유효성 검사등을 하고 service
로 데이터를 넘겨주면 service
에서 요청 받은 데이터를 가공한다. 이때 service에서 Repository
를 사용하여 DB의 데이터를 가져올 수 있다.
nestjs 공식문서를 기준으로 작성
For quickly creating a CRUD controller with the validation built-in, you may use the CLI's CRUD generator:
$ nest g resource [name]
.
--no-spec
을 붙여주면 spec 파일을 제외하고 폴더가 생성된다.
nest g resource users --no-spec
app.module.ts
파일에 자동으로 UsersModule
이 추가된다.
엔티티란 DB의 Table을 mapping 하는 클래스를 의미한다. TypeORM을 이용하여 작성한다.
(Django
로 따지면models.py
에 해당됨)
여러 옵션이 있고 참고 (Column types for mysql - TypeScript) 하여 작성할 것.
...
- name: string - DB에 보여지는 컬럼 명
- length: number - 글자 수
- unique: boolean - 유니크
...
//src/users/user.entity.ts
//user 테이블
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({unique: true}) //별도의 길이 지정이 없는 경우 기본값이 255로 설정 됨.
email: string;
@Column({length: 50, name: 'user_name'})
name: string;
@Column({name: 'user_age'})
age: number;
@Column()
password: string;
}
entity 작성 후 저장 또는
$ npm run start:dev
으로 서버 재실행 해주면 DB에 컬럼이 생성된다.
morgan이란 express의 미들웨어로 백엔드 서버에 log(statusCode)를 남겨주는 좋은 기능이다. (아래 방법은 NestJS용)
Morgan으로 백 서버에 log(statusCode) 표시하기
$ npm install --save class-validator class-transformer
class-validator
란.
Data Validation(데이터 유효성 검사)을 하여 다루는 데이터가 올바른 Format을 가지는지 확인하는 과정 (@IsString 같은 데코레이터를 사용할 수 있다.)
사이트 참고 - typestack/class-validator - GitHub
테이블에 값 생성 및 수정하는 기능은 DTO를 생성하여 유효성 검사를 한다.
Primary Key의 경우 유효성 검사는 필요없다.
//create-user.dto.ts
import { IsEmail, IsNumber, IsString } from "class-validator";
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
name: string;
@IsNumber()
age: number;
@IsString()
password: string;
}
$ npm install class-transformer
class-transformer
란.
plain 객체는 object 클래스의 인스턴스 객체이다. {} 표기법을 통해 생성되며, 리터럴 객체라고도 부른다.
클래스 객체는 자체 정의된 생성자, 속성, 메서드가 있는 클래스의 인스턴스이다.
리터럴 객체를 보유한 클래스의 인스턴스를 매핑할 수 있도록 해준다.
사이트 참고 - TypeScript로 개발 할 때 유용한 라이브러리 - shovelman
//main.ts
import { ValidationPipe } from '@nestjs/common';
...
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidUnknownValues: true,
transform: true
}))
await app.listen(3000);
...
pipe
는 아래 두 가지 기능을 담당한다.
변환(transformation)
: 입력 데이터를 원하는 형식으로 변환(예: 문자열에서 정수로)유효성 검사(validation)
: 입력 데이터를 평가하고 유효한 경우 변경하지 않고 전달. 그렇지 않으면 데이터가 올바르지 않을 때 예외를 발생.
사이트 참고 - Pipes - NestJS Doc
useGlobalPipes
이란.
인터셉터를 전역 인터셉터로 등록한다. (모든 HTTP 라우트 핸들러 내에서 사용)
ValidationPipe
이란.
Nest에는 기본적으로 사용할 수 있는 여러 내장된 파이프가 함께 제공되고 그 중 하나가ValidationPipe
users 모듈에서 typeorm을 사용할 수 있도록 TypeOrm 모듈을 추가하고
db와 매핑되는 entity를 forFeature에 추가한다.
//users.module.ts
...
import { UsersService } from './users.service';
...
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
...
exports: [UsersService]
})
...
컨트롤러는 request, response 데이터를 전달해주는 파일이다. (장고의 urls.py와 같다)
//users.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
//usersService에 작성한 create를 실행하고 createUserDto를 이용해 유효성 검사를 한다.
}
}
컨트롤러에서 넘어 온 데이터를 가공한다. (장고의 views.py와 같다)
//users.service.ts
import { ConflictException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>, //repository를 이용해 DB에 접근한다.
) {}
async create(createUserDto: CreateUserDto): Promise<any> {
const isExist = await this.userRepository.findOne({email: createUserDto.email});
if (isExist) {
throw new ConflictException({
statusCode: HttpStatus.CONFLICT,
message: ['ALREADY_EXIST_ACCOUNT'],
error: 'Conflict'
})
}
const { password, ...result } = await this.userRepository.save(createUserDto);
return result;
}
API : POST localhost:3000/user
Content-Type : application/json (프론트에서 설정)
$ npm install --save bcrypt
$ npm install --save-dev @types/bcrypt
해싱할 때 조금 더 복잡하게 만들어 준다.
//src/constants.ts
export const bcryptConstant = {
saltOrRounds: 10,
};
//users.service.ts
...
import * as bcrypt from 'bcrypt';
import { bcryptConstant } from 'src/constants';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<any> {
...
createUserDto.password = await bcrypt.hash(createUserDto.password, bcryptConstant.saltOrRounds); // hash 코드 추가
const { password, ...result } = await this.userRepository.save(createUserDto);
return result;
}
유저 정보 업데이트를 위한 유효성 검사 파일을 작성한다.
PartialType
사용 시 참조하는CreateUserDto
의 전체 컬럼에 대해 옵셔널이 적용되므로 컬럼을 별도로 적어 주지 않아도 된다
//update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id') //파라메터 형식
findOne(@Param('id') id: number) {
return this.usersService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: number, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(+id, updateUserDto);
}
@Delete(':id')
remove(@Param('id') id: number) {
return this.usersService.remove(+id);
}
}
import { ConflictException, HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<any> {
const isExist = await this.userRepository.findOne({email: createUserDto.email});
if (isExist) {
throw new ConflictException({
statusCode: HttpStatus.CONFLICT,
message: ['ALREADY_EXIST_ACCOUNT'],
error: 'Conflict'
})
}
const { password, ...result } = await this.userRepository.save(createUserDto);
return result;
}
async findAll() {
return this.userRepository.find();
}
async findOne(id: number) {
return this.userRepository.findOne(id);
}
async update(id: number, updateUserDto: UpdateUserDto) {
return this.userRepository.update(id, updateUserDto);
}
async remove(id: number) {
return this.userRepository.delete(id);
}
}
# 참고
dto.ts예시
CRUD 예시