A Developing Developer
DAY 67. TypeORM 복습 본문
- TypeORM 설치
(class-vailidator 이슈 발생 시)
- Window
$ npm uninstall class-validator
$ npm i typeorm@0.3.0
$ npm i @nestjs/typeorm mysql
$ npm i class-validator
- Max
$ npm uninstall class-validator
$ npm i @nestjs/typeorm typeorm mysql
$ npm i class-validator
- TypeORM 적
- TypeModul.forRoot({..}) : 모든 모듈에 적용 (데이터베이스)
- TypeOrmModule.forRootAsync : 비동기 작업(데이터베이스와 연결 설정)이 완료될 때까지 응용프로그램 시작을 지연해야하기 때문에 사용한다.
- @nestjs/config : express .env 의 역할을 수행한다. (마찬가지로 class-vailiator 이슈 발생시 삭제 후 설치 후 재설치)
$ npm i @nestjs/config
-.env
DATABASE_HOST="localhost"
DATABASE_PORT=3306
DATABASE_USERNAME="데이터베이스 아이디"
DATABASE_PASSWORD="데이터베이스 비밀번호"
DATABASE_NAME="데이터베이스 이름"
- app.module.ts (config 설정을 전역으로 참조)
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { BoardModule } from './board/board.module';
import { TypeOrmConfigService } from './config/typeorm.config.service';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync({
useClass: TypeOrmConfigService,
}),
BoardModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
- config/typeorm.config.service.ts (.env 파일의 환경변수 읽어오는 버전)
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config/dist';
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';
import { Article } from 'src/board/article.entity';
@Injectable()
export class TypeOrmConfigService implements TypeOrmOptionsFactory {
// 생성자를 통해서 DI
constructor(private readonly configService: ConfigService) {}
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'mysql',
host: this.configService.get<string>('DATABASE_HOST'),
port: this.configService.get<number>('DATABASE_PORT'),
username: this.configService.get<string>('DATABASE_USERNAME'),
password: this.configService.get<string>('DATABASE_PASSWORD'),
database: this.configService.get<string>('DATABASE_NAME'), // 중요: board 데이터베이스는 미리 생성해놓아야 합니다!
entities: [Article],
synchronize: true, // Production 환경에서는 false로 설정해야 합니다.
};
}
}
- Entity & Repository 생성
- article.entity.js
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity({ schema: 'board', name: 'articles' }) // 해당 클래스가 어떤 테이블에 매핑이 되는지
export class Article {
@PrimaryGeneratedColumn({ type: 'int', name: 'id' }) // PK
id: number;
@Column('varchar', { length: 10 }) // 컬럼
author: string;
@Column('varchar', { length: 50 })
title: string;
@Column('varchar', { length: 1000 })
content: string;
@Column('varchar', { select: false })
password: string;
@CreateDateColumn() // 날짜
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn() // 실제로 삭제하는 것이 아닌 Soft Delete 를 위한 삭제 날짜
deletedAt: Date | null; // deleteAt !== NULL 삭제가 되지 않은 게시물
}
- 일반 Repository 사용은 서비스 생성자에 아래와 같이 주입하면 된다.
constructor(
@InjectRepository(Article) private articleRepository: Repository<Article>
) {}
- board.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Article } from './article.entity';
import { BoardController } from './board.controller';
import { BoardService } from './board.service';
@Module({
// 매우 중요: 서비스에서 사용할 리포지토리 사용을 imports에 명시!
imports: [TypeOrmModule.forFeature([Article])],
controllers: [BoardController],
providers: [BoardService],
})
export class BoardModule {}
! 특정 모듈 서비스에서 사용하고 싶은 레파지토리가 있다면 @Module 데코레이터의 imports 속성에 반드시 넣어줘야 DI가 원활하게 된다.
- board.service.ts (최종)
import _ from 'lodash';
import { Injectable } from '@nestjs/common';
import {
NotFoundException,
UnauthorizedException,
} from '@nestjs/common/exceptions';
import { InjectRepository } from '@nestjs/typeorm';
import { Article } from './article.entity';
import { Repository } from 'typeorm';
@Injectable()
export class BoardService {
constructor(
@InjectRepository(Article) private articleRepository: Repository<Article>,
) {}
// 게시물 목록
async getArticles() {
return await this.articleRepository.find({
where: { deletedAt: null },
select: ['id', 'author', 'title', 'createdAt'],
});
}
// 게시물 상세
async getArticleById(id: number) {
return await this.articleRepository.findOne({
where: { id, deletedAt: null },
select: ['author', 'title', 'content', 'createdAt', 'updatedAt'],
});
}
// 게시물 작성
createArticle(title: string, content: string, password: number) {
this.articleRepository.insert({
author: 'test',
title,
content,
// password도 일단은 잠시 숫자를 문자열로 바꿉니다.
// 나중에 암호화된 비밀번호를 저장하도록 하겠습니다.
password: password.toString(),
});
}
// 게시물 수정
async updateArticle(
id: number,
title: string,
content: string,
password: number,
) {
await this.verfiyPassword(id, password);
this.articleRepository.update(id, { title, content });
}
// 게시물 삭제
async deleteArticle(id: number, password: number) {
await this.verfiyPassword(id, password);
// 바인딩
this.articleRepository.softDelete(id);
}
private async verfiyPassword(id: number, password: number) {
const article = await this.articleRepository.findOne({
where: { id, deletedAt: null },
select: ['password'],
});
if (_.isNil(article)) {
throw new NotFoundException('Article not found. id :' + id);
}
if (article.password !== password.toString()) {
throw new UnauthorizedException(`Password is not corrected. id : ${id}`);
}
}
}
- board.contorller.rs (최종)
import { Controller, Delete, Get, Post, Put } from '@nestjs/common';
import { Body, Param } from '@nestjs/common/decorators';
import { BoardService } from './board.service';
import { CreateArticleDto } from './create-article.dto';
import { DeleteArticleDto } from './delete-article.dto';
import { UpdateArticleDto } from './update-article.dto';
@Controller('board')
export class BoardController {
// 서비스 주입
constructor(private readonly boardService: BoardService) {}
// 게시물 목록을 가져오는 API
@Get('/articles')
async getArticles() {
return await this.boardService.getArticles();
}
// 게시물 상세보기 -> 게시물 ID로 확인
@Get('/articles/:id')
async getArticleById(@Param('id') articleId: number) {
// string
// class-validator, class0transformer
return await this.boardService.getArticleById(articleId);
}
// 게시물 작성
@Post('/articles')
createArticle(@Body() data: CreateArticleDto) {
return this.boardService.createArticle(
data.title,
data.content,
data.password,
);
}
// 게시물 수정
@Put('/articles/:id')
async updateArticle(
@Param('id') articleId: number,
@Body() data: UpdateArticleDto,
) {
return await this.boardService.updateArticle(
articleId,
data.title,
data.content,
data.password,
);
}
// 게시물 삭제
@Delete('/articles/:id')
async deleteArticle(
@Param('id') articleId: number,
@Body() data: DeleteArticleDto,
) {
return await this.boardService.deleteArticle(articleId, data.password);
}
}
! return 을 해야할 부분을 확실히 확인하고 return 해주자!
'내일배움캠프 4기 > TIL' 카테고리의 다른 글
DAY 69. nest.js 심화 (0) | 2023.02.21 |
---|---|
DAY 68. 이론 공부(3) (2) | 2023.02.21 |
DAY 66. TypeORM 실습 (0) | 2023.02.16 |
DAY 65. 이론 공부(2) (0) | 2023.02.15 |
DAY 64. 이론 공부(1) (0) | 2023.02.14 |