1. TypeScript에서의 ? 의미
coin?: number;
- TypeScript에서는 해당 필드를 생략 가능하게 합니다 (
undefined 허용).
- 내부적으로는
coin: number | undefined와 동일합니다.
2. REST용 DTO에서의 Optional 필드 처리
export class CreateMenuDto {
@IsOptional()
@IsNumber()
coin?: number;
}
동작 방식
| 클라이언트 요청 JSON | 서버 수신 값 (coin) | 유효성 검증 통과 여부 |
|---|
{} | undefined | O |
{ "coin": null } | null | X (IsNumber() 실패) |
{ "coin": 4 } | 4 | O |
null 허용하고 싶을 경우
@IsOptional()
@Transform(({ value }) => value === null ? undefined : value)
@IsNumber()
coin?: number;
또는
@IsOptional()
@IsNumber()
@ValidateIf((_, v) => v !== null)
coin?: number;
3. GraphQL용 DTO에서의 Optional 필드 처리
@InputType()
export class CreateMenuInput {
@Field(() => Int, { nullable: true })
coin?: number;
}
동작 방식
| 클라이언트 요청 GraphQL Query | 서버 수신 값 (coin) |
|---|
{} (생략) | null |
{ coin: null } | null |
{ coin: 4 } | 4 |
- GraphQL에서는
nullable: true를 설정해야 클라이언트가 필드를 생략할 수 있음
- 생략된 필드도 자동으로
null로 전달됨 (절대 undefined 아님)
4. gRPC(ts-proto)에서의 Optional 필드 처리
message CreateMenuRequest {
string name = 1;
optional int32 coin = 2;
}
→ ts-proto로 변환 시:
export interface CreateMenuRequest {
name: string;
coin?: number;
}
주의사항
null을 넘기면 타입 에러 발생
- 반드시
undefined만 허용
안전한 호출 예시
await this.grpcService.createMenu({
name: input.name,
coin: input.coin ?? undefined,
});
또는 조건부 spread:
const grpcInput = {
name: input.name,
...(input.coin != null && { coin: input.coin }),
};
5. 비교 표: REST vs GraphQL
| 항목 | REST DTO | GraphQL DTO |
|---|
| 생략 시 | undefined | 자동으로 null으로 들어옴 |
| null 허용 여부 | 기본적으로 허용하지 않음 (IsNumber 실패) | nullable: true 설정 시 허용됨 |
?만 쓰면? | OK (타입 및 validation 통과) | ❌ GraphQL 스키마엔 영향 없음 (nullable: false됨) |
| nullable 설정 | 무의미 | 필수 (nullable: true)로 생략 가능 설정 필요 |
6. 결론 정리
- REST에서는
? + @IsOptional()로 undefined만 허용하는 게 일반적
- GraphQL에서는
nullable: true로 설정하고 내부에서 null → undefined로 변환 필요
- gRPC는
null 절대 허용 안 됨 → 항상 undefined로 변환해서 넘겨야 함
7. 유틸 함수 예시
function convertNullsToUndefined<T extends object>(obj: T): T {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, v === null ? undefined : v])
) as T;
}
await this.grpcService.createMenu(
convertNullsToUndefined(input)
);
8. Entity (*.entity.ts)에서의 Optional 필드 처리
@Entity('menus')
export class Menu {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ nullable: true })
coin?: number;
@Column({ nullable: true })
categoryId?: number;
}
정리
| 설정 | 의미 |
|---|
coin?: number | TypeScript 상 optional (undefined 가능) |
@Column({ nullable: true }) | DB에서 NULL 허용 (컬럼 자체가 null 가능) |
저장 시 동작
| DTO 값 | 저장 결과 |
|---|
undefined | 해당 컬럼 생략 (쿼리에서 빠짐) |
null | DB에 null 값으로 명시적 저장 |
숫자 (e.g. 4) | 해당 숫자 저장 |
✅ nullable: true 없으면 null 저장 시 DB 에러 발생