nestjs에서 enttiy를 작성할 때, 공통적으로 들어가는 컬럼들을 abstarct entity로 빼서 extends 를 해서 사용하는데, 그러면 abstarct class란 대체 뭘까?
🔥 abstract class BaseEntity의 역할
✅ abstract 키워드는 추상 클래스를 정의할 때 사용됨.
✅ BaseEntity를 직접 인스턴스화할 수 없고, 다른 엔티티가 extends 해서만 사용할 수 있음.
✅ typeorm에서 공통 필드를 관리하는 베이스 엔티티를 만들 때 자주 사용됨.
✅ 1. abstract class란?
📌 추상 클래스는 직접 인스턴스화할 수 없고, 자식 클래스에서 상속받아 사용해야 함.
abstract class Animal {
abstract makeSound(): void; // ✅ 추상 메서드 (자식 클래스에서 구현해야 함)
move(): void {
console.log('Moving...');
}
}
class Dog extends Animal {
makeSound() {
console.log('Bark! 🐶');
}
}
const dog = new Dog();
dog.makeSound(); // ✅ "Bark! 🐶"
dog.move(); // ✅ "Moving..."
✅ 추상 클래스는 abstract 메서드를 가질 수 있으며, 자식 클래스에서 반드시 구현해야 함.
✅ Dog 클래스는 Animal을 상속했기 때문에 makeSound()를 구현해야 함.
✅ 2. BaseEntity에서 abstract의 역할
📌 NestJS + TypeORM에서 BaseEntity를 abstract로 선언하면:
- ✅ 공통 필드 (createdAt, updatedAt, useYn)를 모든 엔티티에서 자동으로 상속
- ✅ 직접 인스턴스화할 수 없음 (new BaseEntity() 불가)
- ✅ 자식 엔티티(UserEntity, OrderEntity 등)에서 extends BaseEntity로 상속받아 사용
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { BaseEntity } from './base.entity';
@Entity()
export class UserEntity extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
✅ 이제 UserEntity는 BaseEntity의 필드(createdAt, updatedAt, useYn)를 자동으로 상속!
✅ UserEntity 테이블을 생성하면 created_at, updated_at, use_yn 컬럼이 자동 포함됨.
✅ 3. abstract를 쓰지 않으면?
📌 abstract를 제거하면 BaseEntity를 직접 인스턴스화할 수 있음.
const base = new BaseEntity(); // ❌ 이런 식으로 사용 가능해짐 (원치 않는 동작)
✅ 우리는 BaseEntity를 직접 사용할 목적이 아니라 상속용으로만 쓰고 싶기 때문에 abstract를 사용!
✅ 4. abstract class vs interface 차이점
abstact class | interface | |
상속 방식 | extends 사용 | implements 사용 |
필드(변수) 포함 가능? | ✅ 가능 | ❌ 불가능 (구현해야 함) |
메서드 구현 가능? | ✅ 가능 | ❌ 불가능 (구현 필요) |
인스턴스 생성 가능? | ❌ 불가능 | 🚫 개념 없음 |
예제 | abstract class BaseEntity {} | interface IUser {} |
✅ 공통 컬럼을 가진 엔티티라면 abstract class를 사용하는 것이 좋음!
✅ interface는 typeorm에서 엔티티 정의에 사용할 수 없으므로 abstract class가 적절한 선택.
🔥 NestJS에서 abstract class를 사용하는 경우
✅ NestJS에서도 abstract class를 사용하지만, Spring처럼 자주 사용되지는 않음.
✅ TypeScript는 다중 상속을 지원하지 않기 때문에, 인터페이스(interface)와 조합해서 사용하는 경우가 많음.
✅ 하지만 공통 로직이 많거나 Entity, Service, Controller에서 재사용할 부분이 있다면 abstract class가 유용함!
✅ 1. abstract class를 가장 많이 사용하는 경우
📌 NestJS에서 abstract를 주로 사용하는 곳은 다음과 같음.
사용위치 | 설명 |
1️⃣ BaseEntity | 공통 엔티티 필드 (createdAt, updatedAt) 상속 |
2️⃣ AbstractService | CRUD 로직을 공통적으로 관리할 때 |
3️⃣ AbstractController | 공통 API 엔드포인트를 묶을 때 |
4️⃣ Repository 패턴 | TypeORM + @InjectRepository를 사용하는 경우 |
✅ 아래에서 실제 예제와 데이터를 보면서 하나씩 확인해 보자!
✅ 2. abstract class BaseEntity (공통 엔티티)
📌 Spring에서도 공통 Entity를 @MappedSuperclass로 빼듯이, NestJS에서도 BaseEntity로 만듦.
import { PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, Column } from 'typeorm';
export abstract class BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@CreateDateColumn({ name: 'created_at', type: 'timestamp', comment: '생성일' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp', comment: '수정일' })
updatedAt: Date;
@Column({ name: 'use_yn', type: 'char', length: 1, default: 'Y', comment: '사용 여부' })
useYn: string;
}
✅ 사용 예시 (UserEntity, OrderEntity에서 BaseEntity 상속)
import { Entity, Column } from 'typeorm';
import { BaseEntity } from './base.entity';
@Entity()
export class UserEntity extends BaseEntity {
@Column()
name: string;
@Column({ unique: true })
email: string;
}
💡 Spring에서 @MappedSuperclass와 같은 역할!
💡 모든 엔티티에서 createdAt, updatedAt, useYn을 자동으로 상속받음.
✅ 3. abstract class AbstractService (공통 서비스)
📌 Spring의 @Service에서 공통 CRUD 메서드를 AbstractService로 만들듯이, NestJS에서도 가능!
import { Repository, DeepPartial, FindOneOptions, FindManyOptions } from 'typeorm';
export abstract class AbstractService<T> {
constructor(protected readonly repository: Repository<T>) {}
async create(data: DeepPartial<T>): Promise<T> {
return this.repository.save(data);
}
async findAll(options?: FindManyOptions<T>): Promise<T[]> {
return this.repository.find(options);
}
async findOne(options: FindOneOptions<T>): Promise<T | null> {
return this.repository.findOne(options);
}
async update(id: number, data: Partial<T>): Promise<void> {
await this.repository.update(id, data);
}
async delete(id: number): Promise<void> {
await this.repository.delete(id);
}
}
✅ 사용 예시 (UserService에서 AbstractService 상속)
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AbstractService } from './abstract.service';
import { UserEntity } from '../entities/user.entity';
@Injectable()
export class UserService extends AbstractService<UserEntity> {
constructor(@InjectRepository(UserEntity) repository: Repository<UserEntity>) {
super(repository);
}
}
💡 Spring에서 BaseService를 만들어 재사용하는 것과 유사!
💡 CRUD 로직을 AbstractService로 한 번만 만들고, UserService, OrderService 등에서 상속받아 사용 가능!
✅ 4. abstract class AbstractController (공통 컨트롤러)
📌 Spring에서는 @RestController를 공통으로 만들 수 있는데, NestJS에서도 비슷한 방식이 가능!
import { Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
import { AbstractService } from './abstract.service';
export abstract class AbstractController<T> {
constructor(protected readonly service: AbstractService<T>) {}
@Post()
async create(@Body() data: T): Promise<T> {
return this.service.create(data);
}
@Get(':id')
async findOne(@Param('id') id: number): Promise<T | null> {
return this.service.findOne({ where: { id } });
}
@Get()
async findAll(): Promise<T[]> {
return this.service.findAll();
}
@Put(':id')
async update(@Param('id') id: number, @Body() data: Partial<T>): Promise<void> {
return this.service.update(id, data);
}
@Delete(':id')
async delete(@Param('id') id: number): Promise<void> {
return this.service.delete(id);
}
}
✅ 사용 예시 (UserController에서 AbstractController 상속)
import { Controller } from '@nestjs/common';
import { AbstractController } from './abstract.controller';
import { UserEntity } from '../entities/user.entity';
import { UserService } from '../services/user.service';
@Controller('users')
export class UserController extends AbstractController<UserEntity> {
constructor(protected readonly userService: UserService) {
super(userService);
}
}
💡 Spring에서 BaseController를 만들어 재사용하는 것과 동일한 패턴!
💡 UserController, OrderController 등에서 중복된 API 엔드포인트 코드를 최소화할 수 있음.
🔥 최종 결론: NestJS에서 abstract class를 사용하는 경우
적용위치 | 설명 |
✅ BaseEntity | 공통 엔티티 필드 (createdAt, updatedAt, useYn) 관리 |
✅ AbstractService | CRUD 로직을 한 번만 만들고 재사용 |
✅ AbstractController | API 엔드포인트를 공통으로 관리 |
✅ Repository 패턴 | TypeORM + @InjectRepository에서 활용 |
💡 Spring의 @MappedSuperclass, BaseService, BaseController와 유사한 패턴을 NestJS에서도 abstract class로 구현 가능!
🚀 abstract를 활용하면 코드 재사용성이 증가하고 유지보수가 쉬워짐! 🔥