API 설계
올바른 API 유형 선택
| 유형 | 가장 적합한 경우 | 피해야 할 경우 |
|---|
| REST | 공개 API, CRUD, 간단한 클라이언트, IoT | 복잡한 중첩 데이터, 빠른 스키마 반복 |
| GraphQL | 모바일 앱, 복잡한 중첩 데이터, 여러 클라이언트 | 간단한 CRUD, 익숙하지 않은 소규모 팀 |
| tRPC | TypeScript 모노레포, 내부 풀스택 TS API | TypeScript가 아닌 클라이언트, 공개 API |
| gRPC | 고성능 마이크로서비스 통신, 스트리밍 | 브라우저 클라이언트, 간단한 사용 사례 |
REST API 설계
URL 규칙
GET /users ← 목록
GET /users/{id} ← 단일
POST /users ← 생성
PUT /users/{id} ← 교체
PATCH /users/{id} ← 부분 업데이트
DELETE /users/{id} ← 삭제
# 중첩된 리소스
GET /users/{id}/orders
POST /users/{id}/orders
# 액션 (REST 동사만으로 부족한 경우)
POST /orders/{id}/cancel
POST /users/{id}/activate
HTTP 상태 코드
| 코드 | 사용 시점 |
|---|
| 200 | 성공적인 GET, PUT, PATCH |
| 201 | 성공적인 POST (생성) |
| 204 | 성공적인 DELETE (본문 없음) |
| 400 | 유효성 검사 실패, 잘못된 요청 |
| 401 | 인증 누락/유효하지 않음 |
| 403 | 인증되었으나 권한 없음 |
| 404 | 리소스를 찾을 수 없음 |
| 409 | 충돌 (중복, 버전 불일치) |
| 422 | 처리할 수 없는 엔티티 (의미상 오류) |
| 429 | 속도 제한 초과 |
| 500 | 예상치 못한 서버 오류 |
응답 래퍼
{
"data": [...],
"meta": { "total": 100, "page": 1, "perPage": 20 }
}
{ "data": { "id": "1", "name": "James" } }
{
"error": {
"code": "VALIDATION_ERROR",
"message": "유효성 검사 실패",
"details": [{ "field": "email", "issue": "잘못된 형식" }]
}
}
API 설계 원칙
버전 관리 전략
# URL 버전 관리 (가장 눈에 띄고 라우팅하기 쉬움)
/v1/users
/v2/users
# 헤더 버전 관리 (더 깔끔한 URL)
Accept: application/vnd.api+json;version=2
절대 기존 클라이언트를 중단하지 마십시오. 먼저 사용 중단(deprecate) 표시를 한 후 제거하십시오.
페이지네이션
# 오프셋 (간단, 소규모 데이터셋에 적합)
GET /posts?page=2&perPage=20
# 커서 (대규모 데이터셋에 빠름, 무한 스크롤에 사용)
GET /posts?cursor=eyJpZCI6MTAwfQ&limit=20
필터링 및 정렬
GET /orders?status=pending&userId=123
GET /products?sort=-price,name # - 접두사 = 내림차순
GET /products?fields=id,name,price # 희소 필드셋
멱등성
# 멱등성이 없는 작업을 위한 멱등성 키 포함
POST /payments
Idempotency-Key: unique-client-generated-uuid
GraphQL 설계
스키마 설계 규칙
- 비즈니스 도메인을 설명하고 DB 구조는 설명하지 마십시오
- 목록에는 connections 패턴을 사용하십시오 (페이지네이션 준비)
- 뮤테이션은 수정된 객체를 반환합니다
- 유한한 값 집합에는 Enum을 사용하십시오
- 모든 타입과 필드에 설명을 추가하십시오
type Query {
user(id: ID!): User
users(filter: UserFilter, pagination: PaginationInput): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
API 보안 체크리스트