일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- 개발이 취미인 사람
- jpa
- java
- Producer
- 개발자
- react
- It
- AWS
- kafka
- SWIFT
- component
- Sequelize
- front-end
- class
- spring boot
- props
- node.js
- vue
- swagger
- 코틀린
- Nest.js
- restful api
- javascript
- Kotlin
- 조건문
- back-end
- state
- 상속
- 반복문
- file upload
- Today
- Total
개발이 취미인 사람
[Nest.js] Nest.js API 만들기 (6) - TypeORM API서버 적용(CRUD) 본문
[Nest.js] Nest.js API 만들기 (6) - TypeORM API서버 적용(CRUD)
RyanSin 2021. 11. 13. 19:23- 지난 시간
안녕하세요. 지난 시간에는 TypeORM 설정 및 연결하는 방법에 대해 알아봤습니다.
혹시 놓치고 오신 분들은 아래 링크를 통해 학습하고 오시는 걸 추천드리겠습니다.
[Nest.js] Nest.js API 만들기 (5) - TypeORM 개념 및 설치
[Nest.js] Nest.js API 만들기 (5) - TypeORM 개념 및 설치
- 개요 안녕하세요. 이번 시간에는 TypeORM을 통해 실제 데이터베이스와 연동하고 CRUD를 해보는 시간을 가져보겠습니다. TypeORM은 ORM 기술 중 하나로 "객체와 관계형 데이터 베이스를 매핑(연결)을
any-ting.tistory.com
- 개요
이번 시간에는 이때까지 만들었던 RESTFul API 서버를 수정해보겠습니다.
작업 순서는 아래와 같습니다.
- DTO 수정
- UserController 수정
- UserService 수정
- Repository CRUD 메서드 만들기
- Middleware 코드 주석
- DTO 수정
기존 소스 코드
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { PartialType } from '@nestjs/mapped-types';
/**
* @description SRP를 위반하는 구조이지만 테스트용으로 한 파일에 두 클래스를 선언했다.
*
* SRP란: 한 클래스는 하나의 책임만 가져야한다. (단일 책임의 원칙)
*/
export class CreateUserDto {
@IsNumber()
@IsNotEmpty()
id: number; // 유저 고유 아이디
@IsString()
@IsNotEmpty()
name: string; // 유저 이름
}
export class UpdateUserDto extends PartialType(CreateUserDto) {}
수정된 소스 코드
import {
IsNotEmpty,
IsNumber,
IsString,
Matches,
MaxLength,
MinLength,
} from 'class-validator';
import { PartialType } from '@nestjs/mapped-types';
/**
* @description SRP를 위반하는 구조이지만 테스트용으로 한 파일에 두 클래스를 선언했다.
*
* SRP란: 한 클래스는 하나의 책임만 가져야한다. (단일 책임의 원칙)
*/
export class CreateUserDto {
@IsString()
@IsNotEmpty()
user_id: string; // 유저 아이디
@IsString()
@IsNotEmpty()
@MinLength(8)
@MaxLength(16)
// 최소 8자 및 최대 16자, 하나 이상의 대문자, 하나의 소문자, 하나의 숫자 및 하나의 특수 문자
@Matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,16}$/,
{
message: '비밀번호 양식에 맞게 작성하세요.',
},
)
password: string; //유저 비밀번호
@IsString()
@IsNotEmpty()
name: string; // 유저 이름
@IsNumber()
@IsNotEmpty()
age: number; //유저 나이
}
export class UpdateUserDto extends PartialType(CreateUserDto) {
@IsString()
id: string;
}
속성이 많이 변경된 걸 알 수 있습니다.
Entity 기준으로 필요한 속성을 추가했으며 필요한 유효성 체크 기능을 추가했습니다.
그리고 Update시에는 유저 고유 아이디 값이 필요하기 때문에 id속성을 추가했습니다.
@Matches 옵션은 비밀번호 유효성 체크 기준을 만족시키지 못하면 메시지를 반환합니다.
- UserController 수정
기존 소스 코드
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Put,
Query,
UsePipes,
ParseIntPipe,
ValidationPipe,
DefaultValuePipe,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get() //경로를 설정하지 않으면 "user/" 경로로 설정이 된다.
getHelloWorld(): string {
return this.userService.getHelloWorld();
}
/**
* @author Ryan
* @description @Body 방식 - @Body 어노테이션 여러개를 통해 요청 객체를 접근할 수 있습니다.
*
* CreateUserDto를 사용해서 @Body 전달 방식을 변경합니다.
*
* @param id 유저 고유 아이디
* @param name 유저 이름
*/
@Post('/create_user')
@UsePipes(ValidationPipe)
onCreateUser(@Body() createUserDto: CreateUserDto): User[] {
return this.userService.onCreateUser(createUserDto);
}
/**
* @author Ryan
* @description 전체 유저 조회
*/
@Get('/user_all')
getUserAll(): User[] {
return this.userService.getUserAll();
}
/**
* @author Ryan
* @description @Query 방식 - 단일 유저 조회
*
* @param id 유저 고유 아이디
*/
@Get('/user')
findByUserOne1(
@Query('id', new DefaultValuePipe(1), ParseIntPipe) id: number,
): User {
return this.userService.findByUserOne(id);
}
/**
* @author Ryan
* @description @Param 방식 - 단일 유저 조회
*
* @param id 유저 고유 아이디
*/
@Get('/user/:id')
findByUserOne2(@Param('id', ParseIntPipe) id: number): User {
return this.userService.findByUserOne(id);
}
/**
* @author Ryan
* @description @Param & @Body 혼합 방식 - 단일 유저 수정
*
* @param id 유저 고유 아이디
* @param name 유저 이름
*/
@Patch('/user/:id')
@UsePipes(ValidationPipe)
setUser(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
): User {
return this.userService.setUser(id, updateUserDto);
}
/**
* @author Ryan
* @description @Body 방식 - 전체 유저 수정
*
* @param updateUserDto 유저 정보
*/
@Put('/user/update')
@UsePipes(ValidationPipe)
setAllUser(@Body() updateUserDto: UpdateUserDto): User[] {
return this.userService.setAllUser(updateUserDto);
}
/**
* @author Ryan
* @description @Query 방식 - 단일 유저 삭제
*
* @param id 유저 고유 아이디
*/
@Delete('/user/delete')
deleteUser(@Query('id', ParseIntPipe) id: number): User[] {
return this.userService.deleteUser(id);
}
}
수정된 소스 코드
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Put,
Query,
UsePipes,
ValidationPipe,
ParseUUIDPipe,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
import { User } from 'src/entity/user.entity';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get() //경로를 설정하지 않으면 "user/" 경로로 설정이 된다.
getHelloWorld(): string {
return this.userService.getHelloWorld();
}
/**
* @author Ryan
* @description @Body 방식 - @Body 어노테이션 여러개를 통해 요청 객체를 접근할 수 있습니다.
*
* CreateUserDto를 사용해서 @Body 전달 방식을 변경합니다.
*
* @param id 유저 고유 아이디
* @param name 유저 이름
*/
@Post('/create_user')
@UsePipes(ValidationPipe)
onCreateUser(@Body() createUserDto: CreateUserDto): Promise<boolean> {
return this.userService.onCreateUser(createUserDto);
}
/**
* @author Ryan
* @description 전체 유저 조회
*/
@Get('/user_all')
getUserAll(): Promise<User[]> {
return this.userService.getUserAll();
}
/**
* @author Ryan
* @description @Query 방식 - 단일 유저 조회
*
* @param id 유저 고유 아이디
*/
@Get('/user')
findByUserOne1(@Query('id', ParseUUIDPipe) id: string): Promise<User> {
return this.userService.findByUserOne(id);
}
/**
* @author Ryan
* @description @Param 방식 - 단일 유저 조회
*
* @param id 유저 고유 아이디
*/
@Get('/user/:id')
findByUserOne2(@Param('id', ParseUUIDPipe) id: string): Promise<User> {
return this.userService.findByUserOne(id);
}
/**
* @author Ryan
* @description @Param & @Body 혼합 방식 - 단일 유저 수정
*
* @param id 유저 고유 아이디
* @param name 유저 이름
*/
@Patch('/user/:id')
@UsePipes(ValidationPipe)
setUser(
@Param('id', ParseUUIDPipe) id: string,
@Body() updateUserDto: UpdateUserDto,
): Promise<boolean> {
return this.userService.setUser(id, updateUserDto);
}
/**
* @author Ryan
* @description @Body 방식 - 전체 유저 수정
*
* @param updateUserDto 유저 정보
*/
@Put('/user/update')
@UsePipes(ValidationPipe)
setAllUser(@Body() updateUserDto: UpdateUserDto[]): Promise<boolean> {
return this.userService.setAllUser(updateUserDto);
}
/**
* @author Ryan
* @description @Query 방식 - 단일 유저 삭제
*
* @param id 유저 고유 아이디
*/
@Delete('/user/delete')
deleteUser(@Query('id', ParseUUIDPipe) id: string): Promise<boolean> {
return this.userService.deleteUser(id);
}
}
크게 바뀐 부분은 ParseUUIDPipe와 return 타입을 비동기(Promise <T>) 형식으로 변경했습니다.
TypeORM 메서드는 기본적으로 Promise 객체입니다. 그렇기 때문에 async, await를 사용할 수 있습니다.
- UserService 수정
기존 소스 코드
import { Injectable } from '@nestjs/common';
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
const users: User[] = [
{ id: 1, name: '유저1' },
{ id: 2, name: '유저2' },
{ id: 3, name: '유저3' },
];
@Injectable()
export class UserService {
/**
* @author Ryan
* @description 유저 생성
*
* @param createUserDto 유저 데이터
*
* @returns {User[]} users
*/
onCreateUser(createUserDto: CreateUserDto): User[] {
return users.concat({ id: createUserDto.id, name: createUserDto.name });
}
/**
* @author Ryan
* @description 모든 유저 조회
*
* @returns {User[]} users
*/
getUserAll(): User[] {
return users;
}
/**
* @author Ryan
* @description 단일 유저 조회
*
* @param id 유저 고유 아이디
* @returns {User} users
*/
findByUserOne(id: number): User {
return users.find((data) => data.id == id);
}
/**
* @author Ryan
* @description 단일 유저 수정
*
* @returns {User} users
*/
setUser(id: number, updateUserDto: UpdateUserDto): User {
return users.find((data) => {
if (data.id == id) return (data.name = updateUserDto.name);
});
}
/**
* @author Ryan
* @description 전체 유저 수정
*
* @param updateUserDto 유저 정보
*
* @returns {User[]} users
*/
setAllUser(updateUserDto: UpdateUserDto): User[] {
return users.map((data) => {
if (data.id == updateUserDto.id) {
data.name = updateUserDto.name;
}
return {
id: data.id,
name: data.name,
};
});
}
/**
* @author Ryan
* @description 유저 삭제
*
* @param id
* @returns {User[]} users
*/
deleteUser(id: number): User[] {
return users.filter((data) => data.id != id);
}
getHelloWorld(): string {
return 'Hello World!!';
}
}
수정된 소스 코드
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UserRepository } from 'src/repository/user.repository';
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
import { User } from 'src/entity/user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserRepository) private userRepository: UserRepository,
) {}
/**
* @author Ryan
* @description 유저 생성
*
* @param createUserDto 유저 데이터
*
* @returns {User[]} users
*/
onCreateUser(createUserDto: CreateUserDto): Promise<boolean> {
return this.userRepository.onCreate(createUserDto);
}
/**
* @author Ryan
* @description 모든 유저 조회
*
* @returns {User[]} users
*/
getUserAll(): Promise<User[]> {
return this.userRepository.findAll();
}
/**
* @author Ryan
* @description 단일 유저 조회
*
* @param id 유저 고유 아이디
* @returns {User} users
*/
findByUserOne(id: string): Promise<User> {
return this.userRepository.findById(id);
}
/**
* @author Ryan
* @description 단일 유저 수정
*
* @param id 유저 고유 아이디
* @param updateUserDto 유저 정보
*
* @returns {Promise<boolean>} true
*/
setUser(id: string, updateUserDto: UpdateUserDto): Promise<boolean> {
return this.userRepository.onChnageUser(id, updateUserDto);
}
/**
* @author Ryan
* @description 전체 유저 수정
*
* @param updateUserDto 유저 정보
*
* @returns {Promise<boolean>} true
*/
setAllUser(updateUserDto: UpdateUserDto[]): Promise<boolean> {
return this.userRepository.onChnageUsers(updateUserDto);
}
/**
* @author Ryan
* @description 유저 삭제
*
* @param id
* @returns {Promise<boolean>} true
*/
deleteUser(id: string): Promise<boolean> {
return this.userRepository.onDelete(id);
}
getHelloWorld(): string {
return 'Hello World!!';
}
}
@Service 부분은 특별한 로직이 없기 때문에 해석하기 쉬우실 꺼라 생각합니다.
- Repository CRUD 생성
TypeORM Repository API는 기본적인 CRUD 할 수 있는 메서드를 지원합니다.
Repository API에 대한 자세한 내용은 아래 공식 홈페이지 내용을 참고하시는 걸 추천드리겠습니다. (내용이... 너무 많아요 ㅋㅋ)
https://typeorm.io/#/repository-api
TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server,
typeorm.io
소스 코드
import { NotFoundException } from '@nestjs/common';
import { CreateUserDto, UpdateUserDto } from 'src/user/dto/user.dto';
import { EntityRepository, Repository } from 'typeorm';
import { User } from '../entity/user.entity';
@EntityRepository(User)
export class UserRepository extends Repository<User> {
//유저 생성
async onCreate(createUserDto: CreateUserDto): Promise<boolean> {
const { user_id, password, name, age } = createUserDto;
const user = await this.save({
user_id,
password,
salt: '임시',
name,
age,
});
return user ? true : false;
}
//모든 유저 조회
async findAll(): Promise<User[]> {
return await this.find();
}
//단일 유저 조회
async findById(id: string): Promise<User> {
const user = await this.findOne(id);
if (!user) {
throw new NotFoundException('유저를 찾을 수 없습니다.');
}
return user;
}
//단일 유저 수정
async onChnageUser(
id: string,
updateUserDto: UpdateUserDto,
): Promise<boolean> {
const { name, age } = updateUserDto;
const chnageUser = await this.update({ id }, { name, age });
if (chnageUser.affected !== 1) {
throw new NotFoundException('유저가 존재하지 않습니다.');
}
return true;
}
//전체 유저 수정
async onChnageUsers(updateUserDto: UpdateUserDto[]): Promise<boolean> {
const user = updateUserDto.map((data) => {
return this.update(data.id, { name: data.name, age: data.age });
});
await Promise.all(user);
return true;
}
//유저 삭제
async onDelete(id: string): Promise<boolean> {
/**
* remove() & delete()
* - remove: 존재하지 않는 아이템을 삭제하면 404 Error가 발생합니다.
* - delete: 해당 아이템이 존재 유무를 파악하고 존재하면 삭제하고, 없다면 아무 에러도 발생하지 않는다.
*/
const deleteUser = await this.delete(id);
if (deleteUser.affected === 0) {
throw new NotFoundException('유저가 존재하지 않습니다.');
}
return true;
}
}
TypeORM Repository는 Promise 객체입니다. 이 부분을 잊지 않으시고 사용하시면 어렵지 않으십니다.
Middleware 수정
마지막으로 기존에 사용했던 미들웨어 코드를 주석해서 모두 접근이 가능하게 설정합니다.
import {
Injectable,
NestMiddleware,
UnauthorizedException,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
// // 논리합 연산자 -> 왼쪽 피연산자가 false라면 오른쪽 피연산자가 실행
// const name: string = req.query.name || req.body.name;
// if (name == 'Ryan') {
// next();
// } else {
// // Ryan 유저가 아니라면 허가 받지 않은 유저이기 때문에 401 Error를 반환
// throw new UnauthorizedException();
// }
next();
}
}
천천히 차근차근 실습을 해보시면 쉽게 이해할 수 있습니다. 혹시 이해가 안되는 부분이 있다면 댓글을 꼭 남겨주시면 감사하겠습니다.
소스 저장소
github: https://github.com/Ryan-Sin/Node_Nest/tree/v5
GitHub - Ryan-Sin/Node_Nest
Contribute to Ryan-Sin/Node_Nest development by creating an account on GitHub.
github.com