# API Route 타입 안전성 가이드 ## 📋 개요 Next.js API Route에서 백엔드 API 응답 데이터를 프론트엔드로 전달할 때, TypeScript 타입 정의를 통해 데이터 누락을 방지하는 방법 --- ## 🎯 문제 사례 ### 발생한 이슈 로그인 API를 테스트할 때, API 테스트 도구에서는 `roles` 데이터가 정상적으로 나오지만, 프론트엔드에서는 빈 배열로 나오는 현상 발생 ### 원인 분석 ```typescript // ❌ 타입 정의 없이 데이터 전달 (문제 코드) 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. 백엔드 응답 타입 정의 ```typescript /** * 백엔드 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. 프론트엔드 응답 타입 정의 ```typescript /** * 프론트엔드로 전달할 응답 타입 (토큰 제외) */ 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. 타입 적용 ```typescript 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. 컴파일 타임 에러 감지 ```typescript // ❌ 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()` 시 타입 지정 - [ ] 프론트 응답 객체에 타입 지정 - [ ] 모든 필수 필드 포함 확인 ### 타입 정의 원칙 ```typescript // ✅ 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 (문제 코드) ```typescript 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 (개선 코드) ```typescript 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. 타입과 실제 데이터 불일치 ```typescript // ⚠️ 백엔드 API 스펙 변경 시 interface BackendResponse { // 타입 정의는 그대로인데 user_name: string; } // 실제 응답은 변경됨 { "username": "홍길동" // 필드명 변경됨 } ``` **대응 방안:** - 백엔드 API 스펙 변경 시 타입 정의도 함께 업데이트 - API 응답 검증 로직 추가 (런타임 체크) - 백엔드 팀과 스펙 변경 사전 공유 ### 2. Optional vs Required ```typescript // 명확한 옵셔널 표시 interface Response { required_field: string; // 필수 optional_field?: string; // 선택 nullable_field: string | null; // null 가능 } ``` ### 3. any 타입 남용 금지 ```typescript // ❌ Bad interface Response { data: any; // 타입 안전성 상실 } // ✅ Good interface Response { data: { id: number; name: string; }; } ``` --- ## 📚 관련 문서 - [Authentication Implementation Guide](./[IMPL-2025-11-07]%20authentication-implementation-guide.md) - [Token Management Guide](./[IMPL-2025-11-10]%20token-management-guide.md) - [API Requirements](./[REF]%20api-requirements.md) --- ## 📌 핵심 요약 1. **API Route는 백엔드와 프론트 사이의 중간 레이어** - 데이터 변환/필터링 역할 수행 - 타입 정의로 누락 방지 2. **타입 정의의 3가지 핵심 가치** - 컴파일 타임 에러 감지 - 개발 생산성 향상 (자동완성) - 리팩토링 안정성 보장 3. **실무 적용 원칙** - 백엔드 응답 타입 → 프론트 응답 타입 순서로 정의 - 모든 API Route에 타입 적용 - 백엔드 스펙 변경 시 타입도 함께 업데이트 --- **작성일:** 2025-11-11 **작성자:** Claude Code **마지막 수정:** 2025-11-11