인증, 권한, 에러 처리, 데이터 접근까지 하나씩 정리하고 나면 결국 모든 흐름이 만나는 지점이 드러납니다. 바로 “프론트엔드와 백엔드가 어떻게 약속하고 통신하는가”입니다.
실무에서는 이런 문제가 반복됩니다.
- 백엔드 수정 하나로 프론트 화면 여러 곳이 깨진다
- 응답 형식이 바뀌었는데, 누가 책임져야 하는지 애매하다
- 에러 코드가 제각각이라 공통 처리 로직을 만들 수 없다
- 문서는 있는데, 실제 동작과 맞지 않는다
이 문제의 본질은 기술 선택이 아니라 인터페이스를 계약으로 다루지 않았다는 점에 있습니다. 백엔드 인터페이스를 “구현 결과”가 아니라 “운영 계약”으로 설계하는 기준을 정리합니다.
개념/배경 설명: 인터페이스는 왜 깨지는가
많은 팀에서 인터페이스는 이런 식으로 관리됩니다.
- 일단 동작하게 만든다
- 프론트에서 맞춰서 쓴다
- 문제가 생기면 그때 고친다
이 방식은 초기에는 빠르지만, 운영 단계에 들어가면 비용이 급격히 증가합니다. 특히 다음 상황에서 문제가 커집니다.
- 프론트와 백엔드 배포 주기가 다른 경우
- 웹/모바일/어드민 등 클라이언트가 여러 개인 경우
- 기능 확장보다 안정성이 중요한 시점
그래서 실무에서는 인터페이스를 “내부 구현”이 아니라 깨지면 안 되는 경계로 다룹니다.
핵심 설계 1: 응답 형식을 반드시 고정한다
백엔드 인터페이스에서 가장 먼저 고정해야 할 것은 “응답의 모양”입니다. 성공과 실패가 섞여 있으면, 프론트에서는 항상 예외 처리를 놓치게 됩니다.
// 공통 응답 계약
export type ApiResponse<T> =
| { ok: true; data: T }
| { ok: false; error: { code: string; message?: string } };
이 구조의 핵심은 단순합니다.
- 성공과 실패를 구조적으로 분리한다
- 프론트에서 분기 처리를 강제한다
- HTTP status와 비즈니스 에러를 구분한다
실무 포인트 정리
- 응답 구조는 절대 화면별로 다르게 만들지 않는다
- 에러는 항상 같은 위치에서 내려온다
- 성공/실패 판단은 ok 하나로 끝난다
핵심 설계 2: 에러 코드는 “의미 단위”로 정의한다
에러 메시지를 문자열로만 내려보내면, 프론트에서는 조건 분기가 불가능해집니다. 그래서 에러 코드는 반드시 의미 중심으로 정의해야 합니다.
export type ErrorCode =
| 'UNAUTHORIZED'
| 'FORBIDDEN'
| 'SESSION_EXPIRED'
| 'VALIDATION_FAILED'
| 'RESOURCE_NOT_FOUND';
이렇게 하면 프론트에서는 문자열 비교가 아니라 정책 기반 분기가 가능합니다.
실무 포인트 정리
- 에러 코드는 늘리기 쉽고, 줄이기 어렵다
- 처음부터 과도하게 만들 필요는 없다
- “분기 기준으로 쓸 수 있는가”를 기준으로 정한다
핵심 설계 3: 백엔드 인터페이스는 TypeScript로 계약을 고정한다
문서만으로 인터페이스를 관리하면 언젠가 코드와 어긋나게 됩니다. 그래서 실무에서는 타입을 계약의 기준으로 삼습니다.
// shared/types/api.ts
export type GetProfileResponse = ApiResponse<{
userId: string;
name: string;
email: string;
}>;
이 타입을 기준으로
- 백엔드는 이 형태로만 응답해야 하고
- 프론트는 이 형태만 신뢰한다
이렇게 하면 인터페이스 변경은 곧바로 컴파일 에러로 드러납니다. 운영 사고 대신 개발 단계에서 깨지게 만드는 구조입니다.
핵심 설계 4: 변경 가능성은 “확장 필드”로 흡수한다
모든 변경을 breaking change 없이 처리할 수는 없습니다. 하지만 자주 바뀌는 영역은 처음부터 여지를 두는 것이 좋습니다.
type ListMeta = {
total?: number;
cursor?: string;
};
type ListResponse<T> = ApiResponse<{
items: T[];
meta?: ListMeta;
}>;
이 구조를 쓰면
- 초기에는 meta 없이 단순하게 시작하고
- 나중에 페이징/커서가 필요해져도 계약을 유지할 수 있습니다
운영/실무에서 자주 겪는 문제
- 응답 구조가 API마다 달라 프론트 공통 로직이 없는 경우
- 에러 메시지 변경이 곧 기능 수정이 되는 구조
- 백엔드 수정 후 프론트가 조용히 깨지는 문제
- 문서와 실제 응답이 어긋난 상태로 방치되는 경우
실무 권장 체크리스트
- 모든 API 응답이 동일한 기본 구조를 사용하는가
- 에러 코드를 분기 기준으로 사용할 수 있는가
- 인터페이스 타입이 코드로 관리되고 있는가
- 자주 바뀌는 영역에 확장 여지가 있는가
- 프론트/백엔드가 같은 계약을 기준으로 개발하는가
백엔드 인터페이스 설계의 핵심은 “지금 편한 구현”이 아니라 나중에 깨지지 않는 계약입니다.
응답 구조를 고정하고, 에러를 의미로 정의하고, 타입을 계약으로 사용하면 프론트와 백엔드는 더 이상 눈치 싸움을 하지 않아도 됩니다. 결국 운영이 편해지는 시스템은 인터페이스부터 다릅니다.
'개발 > Typescript' 카테고리의 다른 글
| [TYPESCRIPT] 고급 제네릭 적용 전략: 도메인 모델과 서비스 계층에 안전하게 녹여내는 방법 (0) | 2026.01.23 |
|---|---|
| [TYPESCRIPT] 고급 제네릭 패턴: Higher-Order Type과 Compose Type을 사용하는 이유 (0) | 2026.01.22 |
| [TYPESCRIPT] Nextjs 권한 테스트 전략: Authorization 로직을 테스트로 고정 방법 (0) | 2026.01.19 |
| [TYPESCRIPT] Nextjs 권한(Authorization) 설계 전략: 역할·정책·리소스 모델링 (0) | 2026.01.18 |
| [TYPESCRIPT] Next.js Server Actions vs API Route 선택 전략: 권한·트랜잭션·에러 처리 관점 비교 (0) | 2026.01.17 |
