TypeScript에서 DTO 클래스를 선언하는 다양한 방법 정리
TypeScript로 DTO(Data Transfer Object)를 작성할 때 어떤 방식이 가장 좋을까?
클래스 기반 DTO를 작성할 때의 여러 가지 방법과 상황에 따른 선택 기준을 정리.
(+ 추가로) type / interface / class 의 다른 점도 알아보자!
DTO란?
- Data Transfer Object의 줄임말
- 계층 간 데이터를 주고받기 위한 단순한 데이터 구조
- 프론트엔드에서는 API 요청/응답, props 전달, form 데이터 처리 등에 사용됨
1. 일반적인 클래스 방식
class MenuDto {
public id: number;
public korName: string;
public engName: string;
constructor(id: number, korName: string, engName: string) {
this.id = id;
this.korName = korName;
this.engName = engName;
}
}
- 명시적으로 필드를 선언하고
- 생성자에서 하나하나 값을 할당함
장점
- 코드 가독성이 좋고 구조가 한눈에 들어옴
- 유지보수 시 필드 위치 파악이 쉬움
2. 생성자 축약 방식 (public 키워드 활용)
class MenuDto {
constructor(
public id: number,
public korName: string,
public engName: string
) {}
}
public 키워드를 생성자 매개변수에 붙이면
- 자동으로 클래스 필드가 생성되고 값도 할당됨
장점
- 매우 간결한 코드 작성 가능
- 필드가 많은 DTO일수록 반복을 줄일 수 있음
3. 두 방식 비교 요약
| 항목 | 일반 클래스 방식 | 생성자 축약 방식 |
|---|
| 코드 길이 | 길고 명시적 | 짧고 간결 |
| 가독성 | 높음 | 상대적으로 낮음 |
| 유지보수 | 쉬움 | 구조 파악이 어렵게 느껴질 수 있음 |
| 추천 상황 | 문서화/협업 중요할 때 | 개인 작업, 단순 DTO |
4. protected, private도 가능할까?
class MenuDto {
constructor(
protected id: number,
protected korName: string,
protected engName: string
) {}
}
- 접근 제한자를 바꾸면 외부에서 접근이 불가능해짐
- 주로 상속 관계나 내부 캡슐화가 필요할 때 사용
- ⚠️ 하지만 대부분의 DTO는 데이터를 외부로 노출하므로
public 사용이 일반적
5. type vs interface vs class 의 차이점
- type 방식
type MenuDto {
id: number;
korName: string;
engName: string;
}
- 객체 형태의 데이터 구조를 간단하게 정의할 수 있음
- API 응답, props, form 데이터 타입으로 자주 활용
- interface 방식 (가장 일반적인 프론트엔드 DTO)
interface MenuDto {
id: number;
korName: string;
engName: string;
}
- 생성자 없이 단순하게 타입 정의만 필요할 때
- 대부분의 Next.js/React 프로젝트에서 props, API 응답 타입으로 사용
- type, interface, class 를 비교하면,
| 기준 | type | interface | class |
|---|
| 문법 형태 | 객체 타입 정의 | 객체 타입 정의 | 인스턴스 생성용 |
| 확장 가능성 | &(intersection)으로 가능 | extends로 상속 | extends로 상속 |
| 유연성 | ✅ 가장 유연함 | 객체 타입에 최적화 | 기능 포함 가능 (메서드, constructor 등) |
| 런타임 사용 | ❌ 없음 | ❌ 없음 | ✅ 있음 (JS 변환됨) |
| 사용 목적 | 단순 타입 정의 | 구조 기반 타입 정의 | 메서드, 유효성 검사 등 필요할 때 |
📝 정리
- 상황별 추천 방식 통합 정리
| 상황 | 추천 방식 | 이유 |
|---|
| 간단한 데이터 구조 | type 또는 interface | 선언이 간편하고 유연 |
| API 응답, props 타입 | interface 또는 type | 직관적인 구조 정의 |
| 여러 타입 조합 필요 (Union, Intersection) | type | 타입 연산에 강력함 |
| 생성자/메서드 필요 | class | 인스턴스화, 메서드 정의 가능 |
| NestJS 백엔드 환경 | class + class-validator | 유효성 검사 및 데코레이터 필요 |
| 코드 가독성 중요 | 일반 class 방식 | 명시적 필드 정의로 구조 파악 쉬움 |
| 빠른 정의 필요 | 생성자 축약 class 방식 | 짧은 코드로 동일한 구조 작성 가능 |
- class 스타일 비교 (가독성 vs 축약)
| 방식 | 예시 | 특징 |
|---|
| 일반 class 방식 | class A { id: number; constructor(id: number) { this.id = id; } } | 명시적, 가독성 좋음 |
| 생성자 축약 방식 | class A { constructor(public id: number) {} } | 코드 간결, 구조는 생략됨 |
DTO는 구조가 단순할수록 좋다
코드의 목적과 복잡도에 따라 class, interface를 자유롭게 선택
가장 중요한 건 가독성과 유지보수의 균형!