문제
DB 모델 타입은 하나인데, API 요청마다 필요한 필드가 다르다. 생성할 때는 id 빼고 전부 필수, 수정할 때는 id 필수에 나머지는 선택. 매번 새 인터페이스를 만들면 타입이 끝없이 늘어난다.
해결
유틸리티 타입을 조합하면 기존 타입에서 파생시킬 수 있다.
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
createdAt: Date;
}
// 생성 요청: id, createdAt 제외
type CreateUserDto = Omit<User, 'id' | 'createdAt'>;
// { name: string; email: string; role: 'admin' | 'user' }
// 수정 요청: id 필수, 나머지 선택
type UpdateUserDto = Pick<User, 'id'> & Partial<Omit<User, 'id'>>;
// { id: string; name?: string; email?: string; role?: ... ; createdAt?: Date }
// 목록 응답: 필요한 필드만
type UserListItem = Pick<User, 'id' | 'name' | 'email'>;
// { id: string; name: string; email: string }
자주 쓰는 패턴을 제네릭으로 만들어두면 더 편하다.
// 수정 DTO 패턴: K 필드는 필수, 나머지 선택
type UpdateDto<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>;
// 사용
type UpdateUserDto = UpdateDto<User, 'id'>;
type UpdatePostDto = UpdateDto<Post, 'id' | 'slug'>;
핵심 포인트
Omit<T, K>: 특정 필드 제거 (생성 DTO에 유용)Pick<T, K>: 특정 필드만 선택 (목록/요약 타입에 유용)Partial<T>: 모든 필드를 선택적으로 (수정 DTO에 유용)- 조합하면 새 인터페이스 없이 기존 타입에서 파생 가능
- 자주 쓰는 패턴은 제네릭 타입으로 추출해두면 재사용성이 올라간다
댓글