1. 이전에 만든 게시판 프로젝트에 TypeORM 적용하기
1) TypeORM : Nest.js에서 데이터베이스를 연동하는 수단
2) TypeORM 적용 준비
- @nestjs/config, joi 패키지 설치
npm i @nestjs/config joi
3) app.module.ts에서 DB 생성을 위한 코드 작성
//app.module.ts
import Joi from 'joi';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { Post } from './post/entities/post.entity';
import { PostModule } from './post/post.module';
const typeOrmModuleOptions = {
useFactory: async (
configService: ConfigService,
): Promise<TypeOrmModuleOptions> => ({
type: 'mysql',
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
username: configService.get('DB_USERNAME'),
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_NAME'),
entities: [Post],
synchronize: configService.get('DB_SYNC'),
logging: true,
}),
inject: [ConfigService],
};
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
DB_HOST: Joi.string().required(),
DB_PORT: Joi.number().required(),
DB_USERNAME: Joi.string().required(),
DB_PASSWORD: Joi.string().required(),
DB_NAME: Joi.string().required(),
DB_SYNC: Joi.boolean().required(),
}),
}),
TypeOrmModule.forRootAsync(typeOrmModuleOptions),
PostModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
- AppModule의 Imports에 ConfigModule 추가
- isGlobal:true : 전역적으로 사용한다고 선언
- validationSchema : 정의된 스키마대로 .env가 정의되어있지 않으면 서버는 동작하지 않음
- typeOrmModuleOptions 옵션
- 변수는 TypeORM 모듈의 옵션을 의미
- 하드코딩된 설정을 TypeORM에 주입하지 않고, 동적으로 .env 파일에서 설정을 불러와서 주입 (즉, 값을 그대로 때려박는게 아니라 .env파일에서 불러옴)
- 따라서 useFactory 방법 사용 (TypeOrmModule.forRootAsync 함수에 전달되는 설정 객체를 동적으로 생성하기 위해 사용) > inject: [ConfigService] 옵션을 통해 useFactory에서 사용할 ConfigService를 의존성 주입
- .forRootAsync 와 forRoot의 차이
- forRootAsync는 동적으로 TypeORM 설정을 주입할 때 사용
- forRoot는 하드코딩된 TypeORM 설정을 사용할 때 사용, 잘 안씀
3) 엔티티, 레포지토리 생성
- 엔티티 : 데이터베이스의 특정 테이블을 대표하는 객체, 테이블의 각 로우를 객체로 표현
- 리포지토리 : 엔티티와 데이터베이스 간의 중간 계층을 형성하는 객체, DB와의 통신과정을 알지 못해도 추상화된 리포지토리 함수를 사용해서 DB에서 원하는 결과를 얻을 수 있도록 함
//post.entity.ts
import { IsNumber, IsString } from 'class-validator';
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity({
name: 'posts',
})
export class Post {
@PrimaryGeneratedColumn()
id: number;
@IsString()
@Column('varchar', { length: 50, nullable: false })
title: string;
@IsString()
@Column('varchar', { length: 1000, nullable: false })
content: string;
@IsNumber()
@Column('int', { select: false, nullable: false })
password: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn()
deletedAt?: Date;
}
- @Entity : 해당 클래스가 어떤 테이블에 매핑되는지를 나타내는 어노테이션
- PK에 해당하는 컬럼에는 @PrimaryGeneratedColumn 어노테이션 사용
- 나머지 컬럼에는 @Column, 날짜에 해당하면 @DateColumn 어노테이션 사용
- 비밀번호의 select 옵션 false : 일반적인 조회로는 비밀번호를 알아낼 수 없도록 하여 민감한 정보 보호
- @DeleteDateColumn : 삭제된 날짜를 기록, 이 어노테이션을 사용하면 엔티티 삭제 순간 실제로 삭제되지 않고 논리적 삭제됨 > 전체 게시물을 가져올 때 deletedAt != null인 게시물만 가져옴
//post.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Post } from './entities/post.entity';
import { PostController } from './post.controller';
import { PostService } from './post.service';
@Module({
imports: [TypeOrmModule.forFeature([Post])],
controllers: [PostController],
providers: [PostService],
})
export class PostModule {}
- @Module 데코레이터의 import 속성에 사용할 레포지토리를 넣어줌
//post.service.js
import _ from 'lodash';
import { Repository } from 'typeorm';
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { CreatePostDto } from './dto/create-post.dto';
import { RemovePostDTO } from './dto/remove-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { Post } from './entities/post.entity';
@Injectable()
export class PostService {
private articles: { id: number; title: string; content: string }[] = [];
private articlePasswords = new Map<number, number>();
constructor(
@InjectRepository(Post) private postRepository: Repository<Post>,
) {}
.
.
.
- 생성자 추가 > @InjectRepository 어노테이션을 사용하여 리포지토리 주입
4) 서비스 리팩토링
import _ from 'lodash';
import { Repository } from 'typeorm';
import {
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { CreatePostDto } from './dto/create-post.dto';
import { RemovePostDTO } from './dto/remove-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { Post } from './entities/post.entity';
@Injectable()
export class PostService {
constructor(
@InjectRepository(Post) private postRepository: Repository<Post>,
) {}
async create(createPostDto: CreatePostDto) {
return (await this.postRepository.save(createPostDto)).id;
}
async findAll() {
return await this.postRepository.find({
where: { deletedAt: null },
select: ['id', 'title', 'updatedAt'],
});
}
async findOne(id: number) {
return await this.postRepository.findOne({
where: { id, deletedAt: null },
select: ['title', 'content', 'updatedAt'],
});
}
async update(id: number, updatePostDto: UpdatePostDto) {
const { content, password } = updatePostDto;
const post = await this.postRepository.findOne({
select: ['password'],
where: { id },
});
if (_.isNil(post)) {
throw new NotFoundException('게시물을 찾을 수 없습니다.');
}
if (!_.isNil(post.password) && post.password !== password) {
throw new UnauthorizedException('비밀번호가 일치하지 않습니다.');
}
await this.postRepository.update({ id }, { content });
}
async remove(id: number, removePostDto: RemovePostDTO) {
const { password } = removePostDto;
const post = await this.postRepository.findOne({
select: ['password'],
where: { id },
});
if (_.isNil(post)) {
throw new NotFoundException('게시물을 찾을 수 없습니다.');
}
if (!_.isNil(post.password) && post.password !== password) {
throw new UnauthorizedException('비밀번호가 일치하지 않습니다.');
}
return this.postRepository.softDelete({ id });
}
}
5) RDS 사용할 경우 .env 설정
DB_HOST="express-database....(엔드포인트 주소)"
DB_PORT=3306
DB_USERNAME="데이터베이스 계정"
DB_PASSWORD="데이터베이스 암호"
DB_NAME="nestjsboard"
DB_SYNC=true
로컬에서 사용할거면 DB_HOST에 localhost라고 입력
RDS 생성시 설정했던 username, password 입력, 기억안나면 이전 node.js 프로젝트의 .env 파일 뒤져보자.... 엔드포인트 앞에 있는 2개 그대로 들고오기
'TIL' 카테고리의 다른 글
TIL 240701 - javascript/typescript에서 날짜 한국표준시각으로 변경 (2) | 2024.07.01 |
---|---|
TIL 240628 - Nest.js에서 Provider란? / useValue, useClass, useFactory 사용법 (0) | 2024.06.28 |
TIL 240626 - 면접 예상질문 및 답변 모음 (공개모의면접 진행) (0) | 2024.06.26 |
TIL 240625 - Nest.js란? 간단한 코드 해부하기 (0) | 2024.06.25 |
TIL 240624 - 알고리즘 코드카타 리뷰 - 연속 부분 수열 합의 개수 (2) | 2024.06.24 |