NestJS에서 클래스 기반으로 CRUD 작업을 수행하는 컴포넌트를 만들기 위해, 제시하신 요구 사항을 바탕으로 구체적인 코드를 포스팅을 하겠습니다.
NestJS와 TypeORM을 사용하여 PostgreSQL 데이터베이스에 접근하는 방식을 보여줍니다.
트랜잭션 관리 및 동시성 처리를 포함할 수 있는 방법도 작성하였습니다.
프로젝트의 기본 구조는 다음과 같습니다
src/
├── app.module.ts
├── entities/
│ ├── user.entity.ts
│ └── board.entity.ts
├── dto/
│ ├── create-user.dto.ts
│ ├── update-user.dto.ts
│ ├── create-board.dto.ts
│ └── update-board.dto.ts
├── controllers/
│ └── dynamic.controller.ts
└── services/
└── dynamic.service.ts
여기서는 User와 Board 두 개의 엔티티를 예로 들겠습니다.
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
email: string;
}
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('boards')
export class Board {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
content: string;
}
각 엔티티에 대해 생성 및 업데이트 DTO를 생성합니다.
import { IsEmail, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
username: string;
@IsEmail()
email: string;
}
import { IsEmail, IsOptional } from 'class-validator';
export class UpdateUserDto {
@IsOptional()
username?: string;
@IsOptional()
@IsEmail()
email?: string;
}
import { IsNotEmpty } from 'class-validator';
export class CreateBoardDto {
@IsNotEmpty()
title: string;
@IsNotEmpty()
content: string;
}
import { IsOptional } from 'class-validator';
export class UpdateBoardDto {
@IsOptional()
title?: string;
@IsOptional()
content?: string;
}
CRUD 작업을 처리하는 DynamicService를 생성합니다.
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, EntityTarget, Transaction, TransactionManager, EntityManager } from 'typeorm';
@Injectable()
export class DynamicService {
constructor(
@InjectRepository(User) private userRepository: Repository<User>,
@InjectRepository(Board) private boardRepository: Repository<Board>,
) {}
private getRepository(entityClass: EntityTarget<any>) {
if (entityClass === User) {
return this.userRepository;
} else if (entityClass === Board) {
return this.boardRepository;
}
throw new NotFoundException('Entity not found');
}
@Transaction()
async handleCRUD(
entityClass: EntityTarget<any>,
operation: string,
data: any,
action: string,
@TransactionManager() manager: EntityManager,
) {
const repository = this.getRepository(entityClass);
switch (operation) {
case 'C': // Create
return await repository.save(data);
case 'U': // Update
await repository.update(data.id, data);
return await repository.findOne(data.id);
case 'D': // Delete
await repository.delete(data.id);
return { deleted: true };
default:
throw new Error('Invalid operation');
}
}
}
사용자의 요청을 처리하는 DynamicController를 생성합니다.
import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { DynamicService } from './dynamic.service';
import { User } from '../entities/user.entity';
import { Board } from '../entities/board.entity';
@Controller('dynamic')
export class DynamicController {
constructor(private readonly dynamicService: DynamicService) {}
@Post(':class/:operation')
async create(
@Param('class') className: string,
@Param('operation') operation: string,
@Body() data: any,
) {
const entityClass = this.getEntityClass(className);
return await this.dynamicService.handleCRUD(entityClass, operation, data, 'POST');
}
@Put(':class/:operation')
async update(
@Param('class') className: string,
@Param('operation') operation: string,
@Body() data: any,
) {
const entityClass = this.getEntityClass(className);
return await this.dynamicService.handleCRUD(entityClass, operation, data, 'PUT');
}
@Delete(':class/:operation/:id')
async delete(
@Param('class') className: string,
@Param('operation') operation: string,
@Param('id') id: number,
) {
const entityClass = this.getEntityClass(className);
return await this.dynamicService.handleCRUD(entityClass, operation, { id }, 'DELETE');
}
private getEntityClass(className: string) {
switch (className) {
case 'user':
return User;
case 'board':
return Board;
default:
throw new Error('Invalid class name');
}
}
}
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Board } from './entities/board.entity';
import { DynamicController } from './controllers/dynamic.controller';
import { DynamicService } from './services/dynamic.service';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'your_username',
password: 'your_password',
database: 'your_database',
entities: [User, Board],
synchronize: true,
}),
TypeOrmModule.forFeature([User, Board]),
],
controllers: [DynamicController],
providers: [DynamicService],
})
export class AppModule {}
위 코드에서 트랜잭션 관리는 @Transaction() 데코레이터를 사용하여 서비스 메소드에 적용했습니다.
동시성 문제를 해결하기 위해 PostgreSQL의 optimistic locking을 사용할 수 있습니다.
이 방법을 적용하려면, 엔티티에 @VersionColumn()을 추가하여 버전 관리를 할 수 있습니다.
위 코드는 NestJS와 TypeORM을 활용하여 동적 CRUD 작업을 수행하는 기본적인 구조를 제공합니다.
class, operation, data, action을 파라미터로 받아서 동적으로 CRUD를 처리하며, PostgreSQL을 데이터베이스로 사용하였습니다.
이를 통해 다양한 엔티티에 대해 CRUD 처리를 효율적으로 관리할 수 있었습니다.