- BOM 항목 추가/수정/삭제 시 섹션탭 즉시 반영 - 섹션 복제 시 UI 즉시 업데이트 (null vs undefined 이슈 해결) - 항목 수정 기능 추가 (useTemplateManagement) - 실시간 동기화 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
7.5 KiB
7.5 KiB
API Route 타입 안전성 가이드
📋 개요
Next.js API Route에서 백엔드 API 응답 데이터를 프론트엔드로 전달할 때, TypeScript 타입 정의를 통해 데이터 누락을 방지하는 방법
🎯 문제 사례
발생한 이슈
로그인 API를 테스트할 때, API 테스트 도구에서는 roles 데이터가 정상적으로 나오지만, 프론트엔드에서는 빈 배열로 나오는 현상 발생
원인 분석
// ❌ 타입 정의 없이 데이터 전달 (문제 코드)
const responseData = {
message: data.message,
user: data.user,
tenant: data.tenant,
menus: data.menus,
// roles: data.roles, ← 누락됨!
token_type: data.token_type,
expires_in: data.expires_in,
expires_at: data.expires_at,
};
문제점:
- 백엔드에서
roles데이터를 반환했지만 - Next.js API Route에서 프론트로 전달할 때
roles필드를 포함하지 않음 - 타입 정의가 없어서 컴파일 타임에 감지 불가
✅ 해결 방법
1. 백엔드 응답 타입 정의
/**
* 백엔드 API 로그인 응답 타입
*/
interface BackendLoginResponse {
message: string;
access_token: string;
refresh_token: string;
token_type: string;
expires_in: number;
expires_at: string;
user: {
id: number;
user_id: string;
name: string;
email: string;
phone: string;
};
tenant: {
id: number;
company_name: string;
business_num: string;
tenant_st_code: string;
other_tenants: any[];
};
menus: Array<{
id: number;
parent_id: number | null;
name: string;
url: string;
icon: string;
sort_order: number;
is_external: number;
external_url: string | null;
}>;
roles: Array<{
id: number;
name: string;
description: string;
}>;
}
2. 프론트엔드 응답 타입 정의
/**
* 프론트엔드로 전달할 응답 타입 (토큰 제외)
*/
interface FrontendLoginResponse {
message: string;
user: BackendLoginResponse['user'];
tenant: BackendLoginResponse['tenant'];
menus: BackendLoginResponse['menus'];
roles: BackendLoginResponse['roles']; // ✅ 명시적으로 포함
token_type: string;
expires_in: number;
expires_at: string;
}
3. 타입 적용
export async function POST(request: NextRequest) {
try {
// ... 백엔드 API 호출
// ✅ 타입 지정
const data: BackendLoginResponse = await backendResponse.json();
// ✅ 타입 지정 + 모든 필드 포함
const responseData: FrontendLoginResponse = {
message: data.message,
user: data.user,
tenant: data.tenant,
menus: data.menus,
roles: data.roles, // ✅ 누락 방지
token_type: data.token_type,
expires_in: data.expires_in,
expires_at: data.expires_at,
};
return NextResponse.json(responseData, { status: 200 });
} catch (error) {
// ... 에러 처리
}
}
🎁 타입 정의의 장점
1. 컴파일 타임 에러 감지
// ❌ roles 누락 시 TypeScript 에러 발생
const responseData: FrontendLoginResponse = {
message: data.message,
user: data.user,
// ... roles 필드 빠짐
// ⚠️ Type Error: Property 'roles' is missing in type
};
2. 자동 완성 지원
- IDE에서 필드명 자동 완성
- 오타 방지
- 개발 생산성 향상
3. API 문서 역할
- 백엔드 API 스펙이 코드에 명시됨
- 별도 문서 없이도 데이터 구조 파악 가능
- 팀원 간 커뮤니케이션 비용 절감
4. 리팩토링 안정성
- 백엔드 API 변경 시 즉시 감지
- 영향 범위 파악 용이
- 안전한 코드 수정
📝 적용 체크리스트
API Route 작성 시 필수 사항
- 백엔드 응답 타입 인터페이스 정의
- 프론트엔드 응답 타입 인터페이스 정의
await response.json()시 타입 지정- 프론트 응답 객체에 타입 지정
- 모든 필수 필드 포함 확인
타입 정의 원칙
// ✅ Good: 명시적 타입 지정
const data: BackendResponse = await response.json();
const result: FrontendResponse = {
// ... 모든 필드 포함
};
// ❌ Bad: 타입 없이 작성
const data = await response.json();
const result = {
// ... 필드 누락 가능성
};
🔍 실제 적용 예시
파일 위치
src/app/api/auth/login/route.ts
Before (문제 코드)
export async function POST(request: NextRequest) {
// ...
const data = await backendResponse.json(); // 타입 없음
const responseData = {
message: data.message,
user: data.user,
menus: data.menus,
// roles 누락!
};
return NextResponse.json(responseData);
}
After (개선 코드)
interface BackendLoginResponse {
// ... 전체 타입 정의
roles: Array<{ id: number; name: string; description: string }>;
}
interface FrontendLoginResponse {
// ... 전체 타입 정의
roles: BackendLoginResponse['roles'];
}
export async function POST(request: NextRequest) {
// ...
const data: BackendLoginResponse = await backendResponse.json();
const responseData: FrontendLoginResponse = {
message: data.message,
user: data.user,
menus: data.menus,
roles: data.roles, // ✅ 명시적 포함
// ... 기타 필드
};
return NextResponse.json(responseData);
}
🚨 주의사항
1. 타입과 실제 데이터 불일치
// ⚠️ 백엔드 API 스펙 변경 시
interface BackendResponse {
// 타입 정의는 그대로인데
user_name: string;
}
// 실제 응답은 변경됨
{
"username": "홍길동" // 필드명 변경됨
}
대응 방안:
- 백엔드 API 스펙 변경 시 타입 정의도 함께 업데이트
- API 응답 검증 로직 추가 (런타임 체크)
- 백엔드 팀과 스펙 변경 사전 공유
2. Optional vs Required
// 명확한 옵셔널 표시
interface Response {
required_field: string; // 필수
optional_field?: string; // 선택
nullable_field: string | null; // null 가능
}
3. any 타입 남용 금지
// ❌ Bad
interface Response {
data: any; // 타입 안전성 상실
}
// ✅ Good
interface Response {
data: {
id: number;
name: string;
};
}
📚 관련 문서
📌 핵심 요약
-
API Route는 백엔드와 프론트 사이의 중간 레이어
- 데이터 변환/필터링 역할 수행
- 타입 정의로 누락 방지
-
타입 정의의 3가지 핵심 가치
- 컴파일 타임 에러 감지
- 개발 생산성 향상 (자동완성)
- 리팩토링 안정성 보장
-
실무 적용 원칙
- 백엔드 응답 타입 → 프론트 응답 타입 순서로 정의
- 모든 API Route에 타입 적용
- 백엔드 스펙 변경 시 타입도 함께 업데이트
작성일: 2025-11-11 작성자: Claude Code 마지막 수정: 2025-11-11
관련 파일
프론트엔드
src/app/api/auth/login/route.ts- 로그인 API Routesrc/types/auth.ts- 인증 타입 정의src/lib/api/auth/types.ts- API 인증 타입
참조 문서
claudedocs/auth/[IMPL-2025-11-07] authentication-implementation-guide.mdclaudedocs/auth/[IMPL-2025-11-10] token-management-guide.md