HTTP / REST API 설계
REST는 화려한 기술이 아니라 ‘약속’이다. HTTP가 이미 정의해 둔 메서드와 상태 코드의 의미를 지키면, 별도 문서 없이도 클라이언트가 API의 동작을 예측할 수 있다. 그 약속의 핵심을 정리한다.
HTTP 메서드 의미론
메서드는 ‘리소스에 무엇을 할 것인가’를 나타낸다. 동작을 URL에 담지 않고 (예: /users/delete) 메서드로 표현하는 것이 REST의 출발점이다.
| 메서드 | 의미 | 예시 |
|---|---|---|
| GET | 리소스 조회 | GET /users/42 |
| POST | 리소스 생성 | POST /users |
| PUT | 리소스 전체 교체 | PUT /users/42 |
| PATCH | 리소스 부분 수정 | PATCH /users/42 |
| DELETE | 리소스 삭제 | DELETE /users/42 |
PUT vs PATCH
PUT은 리소스를 통째로 교체하므로 보내지 않은 필드는 비워진다고 본다. PATCH는 보낸 필드만 부분 수정한다. 수정 폼에서 일부만 보낸다면 PATCH가 의미상 맞다.
멱등성과 안전성
API를 견고하게 만들려면 각 메서드의 ‘안전성(safe)’과 ‘멱등성(idempotent)’을 이해해야 한다. 특히 네트워크 재시도 상황에서 중요하다.
안전(Safe)
서버 상태를 바꾸지 않는 메서드다. GET이 대표적이다. 안전한 메서드는 자유롭게 캐싱하고 미리 가져올 수 있다.
멱등(Idempotent)
같은 요청을 여러 번 보내도 결과가 동일한 메서드다. GET·PUT·DELETE는 멱등하지만, POST는 호출할 때마다 새 리소스를 만들어 멱등하지 않다.
재시도와 중복 생성
네트워크 타임아웃으로 POST를 재시도하면 리소스가 중복 생성될 수 있다. 결제·주문 같은 곳에서는 멱등성 키(Idempotency-Key)를 헤더로 받아 중복을 막는다.
상태 코드 체계
상태 코드는 응답의 결과를 한눈에 알려주는 신호다. 200으로 뭉뚱그리고 본문에{ success: false }를 넣는 방식은 HTTP의 의미를 버리는 안티패턴이다.
| 분류 | 의미 | 대표 코드 |
|---|---|---|
| 2xx | 성공 | 200 OK · 201 Created · 204 No Content |
| 3xx | 리다이렉션 | 301 Moved · 304 Not Modified |
| 4xx | 클라이언트 오류 | 400 · 401 · 403 · 404 · 409 |
| 5xx | 서버 오류 | 500 Internal · 503 Unavailable |
자주 헷갈리는 코드
401은 인증 안 됨(누구인지 모름), 403은 인증은 됐지만 권한 없음. 409 Conflict는 중복 가입처럼 현재 상태와 충돌할 때 쓴다.
201과 Location
생성 성공 시 201 Created와 함께 Location 헤더에 새 리소스의 URI를 담아주면 클라이언트가 바로 접근할 수 있다.
RESTful 리소스 설계
URI는 ‘행위’가 아니라 ‘자원(명사)’을 가리켜야 한다. 행위는 메서드가, 대상은 URI가 표현한다.
# ❌ 동사·행위가 URI에 노출
POST /createUser
GET /getUserOrders?userId=42
POST /users/42/delete
# ✅ 명사(자원) 중심, 계층 구조
POST /users # 사용자 생성
GET /users/42 # 단일 조회
GET /users/42/orders # 하위 컬렉션
DELETE /users/42 # 삭제- 컬렉션은 복수형:
/users,/orders처럼 복수 명사로 통일한다. - 계층으로 소유 관계 표현:
/users/42/orders는 42번 사용자의 주문을 의미한다. - 필터·정렬·페이징은 쿼리스트링:
/orders?status=paid&sort=-createdAt&page=2처럼 자원의 부분 집합을 쿼리로 표현한다.
RFC 7807 에러 형식
에러 응답 형식이 API마다 제각각이면 클라이언트가 매번 다르게 파싱해야 한다. RFC 7807(Problem Details)은 에러 본문의 표준 형태를 정의해 이 문제를 해결한다.
HTTP/1.1 409 Conflict
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/duplicate-email",
"title": "이미 사용 중인 이메일입니다",
"status": 409,
"detail": "user@example.com 은 이미 가입된 이메일입니다.",
"instance": "/users"
}표준 필드
type(에러 종류 URI), title(요약), status(HTTP 코드), detail(상세), instance(발생 위치)로 구성된다. 도메인별 필드는 자유롭게 확장한다.
일관성의 가치
모든 에러가 같은 형태를 가지면, 클라이언트는 공통 에러 핸들러 하나로 모든 에러를 처리할 수 있다. 계약(contract)이 명확해진다.
좋은 API는 새 문서를 읽지 않아도 동작이 예측된다.
메서드·상태 코드·자원이라는 HTTP의 약속을 그대로 지키는 것이 곧 REST다.