Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
Tags
- swift
- UIKit
- Android
- 알고리즘
- 알고리즘 공부
- dfs
- HIG
- Algorithm
- 오토레이아웃
- 그리디 알고리즘
- Clean Architecture
- Autolayout
- Kotlin
- 앱개발
- 프로그래머스
- 파이썬 풀이
- ios
- Swift공부
- 정렬
- Python
- 백준온라인저지
- error
- Til
- 백준 온라인 저지
- 안드로이드 공부
- 공부
- 파이썬
- BFS
- iOS개발
- greedy algorithm
Archives
- Today
- Total
Tori의 개발 공부
[Next.js] HTTP 응답 구조를 표준화한 이유와 설계 전략 본문
API 응답은 단순한 데이터 반환이 아니라 프론트와의 계약이다.
1. 왜 응답 구조를 표준화해야 할까?
초기에는 Route Handler에서 다음처럼 직접 응답을 반환했습니다.
return NextResponse.json({ error: '에러 발생' }, { status: 500 })
return NextResponse.json(
{ success: true, data: groupData },
{ status: 201 },
)
겉보기엔 문제 없어 보이지만, 프로젝트가 커질수록 다음 문제가 발생할 위험이 있었습니다:
- 응답 구조가 API마다 달라질 위험
- 에러 필드 구조가 통일되지 않음
- status 코드가 분산 관리됨
- 추후 meta, requestId 추가 시 전체 수정 필요
- 프론트 처리 로직이 복잡해짐
2. 설계 목표
응답 구조화를 위해 다음과 같은 목표를 가지고 설계했습니다:
- 모든 API 응답을 동일한 형태로 유지
- 성공/실패 분기 명확화
- 상태코드 관리 중앙화
- 향후 확장 가능성 확보
3. 해결 전략: Envelope 패턴 도입
응답 타입 정의
export type ApiSuccess<T> = {
success: true
data: T
}
export type ApiError = {
success: false
error: {
message: string
code?: string
details?: unknown
}
}
export type ApiResponse<T> = ApiSuccess<T> | ApiError
모든 API는 반드시 이 두 구조 중 하나만 반환합니다.
이 구조의 핵심은 다음과 같습니다.
- 성공과 실패를 명확히 분리한다.
success: true | false를 기준으로 항상 분기할 수 있기 때문에 프론트엔드 로직 단순화 가능 - 응답을 “데이터”가 아니라 “계약”으로 정의한다.
API는 단순히 값을 반환하는 함수가 아니라, 클라이언트와 약속된 형식을 전달하는 인터페이스 역할 - 확장 가능성을 확보한다.
meta, requestId, timestamp 등의 필드를 추가해야 할 경우, 이 타입 정의만 수정하면 전체 API 계약이 일관되게 유지 가능 - 클라이언트 타입 안정성을 확보한다.
프론트에서는 ApiResponse<T>를 기준으로 응답을 처리할 수 있으며, 잘못된 접근(예: 실패 응답에서 data 접근)을 컴파일 단계에서 방지 가능
4. Response Helper 추상화
export function ok<T>(data: T, status = 200) {
return NextResponse.json({ success: true, data }, { status })
}
export function fail(
message: string,
status = 500,
opts?: { code?: string; details?: unknown },
) {
return NextResponse.json(
{
success: false,
error: {
message,
code: opts?.code,
details: opts?.details,
},
},
{ status },
)
}
그리고 자주 쓰는 상태코드는 별칭으로 제공했습니다.
export const created = <T,>(data: T) => ok(data, 201)
export const badRequest = (message: string) => fail(message, 400)
export const unauthorized = (message = '인증이 필요합니다.') => fail(message, 401)
export const forbidden = (message = '권한이 없습니다.') => fail(message, 403)
export const notFound = (message = '대상을 찾을 수 없습니다.') => fail(message, 404)
5. 결과
1️⃣ 코드 가독성 개선
return created(groupData)
API 라우터 반환에서 반환값의 의도가 명확해졌습니다.
2️⃣ 유지보수성 향상
- 응답 구조 변경 시 한 파일만 수정
- 에러 정책 일괄 적용 가능
- meta, requestId 확장 용이
3️⃣ 프론트 로직 단순화
if (!res.success) {
showToast(res.error.message)
}
이후 UX 처리 로직이 통일시킬 수 있습니다.
마무리
작은 추상화처럼 보이지만, API 응답 표준화는 프로젝트의 확장성과 일관성을 지탱하는 중요한 설계 요소입니다.
응답 구조를 정리하는 일은 단순한 리팩토링이 아니라, 시스템의 계약을 정립하는 작업이라는 생각이 들었습니다.