API를 설계할 때, URI를 어떻게 짓고, 어떤 상태 코드를 보내고, 멱등성이 왜 중요한지 — 이 세 가지를 명확히 아는 것만으로도 API 품질이 달라집니다.

REST는 2025년 Postman 조사 기준으로 93%의 API가 채택하는 가장 보편적인 아키텍처 스타일입니다. gRPC(14%)가 내부 통신에서 성장하고 있지만, 공개 API는 여전히 REST가 표준입니다.


REST란 — Roy Fielding의 6가지 제약 조건

REST(Representational State Transfer)는 Roy Fielding이 2000년 박사 논문에서 정의한 아키텍처 스타일입니다. "프로토콜"이 아니라 "제약 조건의 집합"이라는 점이 중요합니다.

  1. Client-Server: 클라이언트와 서버의 역할 분리
  2. Stateless: 각 요청은 필요한 모든 정보를 포함 (서버가 세션 상태를 저장하지 않음)
  3. Cacheable: 응답은 캐시 가능 여부를 명시해야 함
  4. Layered System: 클라이언트는 중간 계층(프록시, 로드밸런서)의 존재를 알 필요 없음
  5. Uniform Interface: 리소스 식별, 표현을 통한 조작, 자기 서술적 메시지, HATEOAS
  6. Code on Demand (선택): 필요시 서버가 실행 가능한 코드를 전달 (예: JavaScript)

실무에서 "RESTful하다"라고 말할 때, 대부분은 1~4번 + Uniform Interface의 일부만 지키는 수준입니다. 완전한 REST(HATEOAS 포함)를 구현하는 API는 드뭅니다.


URI 설계 규칙

기본 원칙

PLAINTEXT
# 좋은 예
GET /users
GET /users/{id}
GET /users/{id}/orders
POST /users
DELETE /users/{id}

# 나쁜 예
GET /getUsers          ← 동사 사용
POST /user/create      ← 동사 + 단수형
GET /Users/{ID}        ← 대문자
GET /user_list         ← 언더스코어
  • ** 명사 사용 **: 행위는 HTTP 메서드가 표현하므로 URI에 동사를 쓰지 않음
  • ** 복수형 **: 컬렉션을 나타내므로 /users/user보다 자연스러움
  • ** 소문자 + 하이픈 **: /user-profiles (언더스코어나 카멜케이스 지양)
  • ** 계층 관계 **: /users/{id}/orders — 사용자에 속한 주문

필터링, 정렬, 페이지네이션

PLAINTEXT
# 쿼리 파라미터로 처리
GET /users?status=active&sort=created_at&page=2&size=20

# URI에 넣지 않음 (안티패턴)
GET /users/active/sort-by-date/page/2   ← 복잡하고 캐시 불리

검색 엔드포인트

PLAINTEXT
# 간단한 검색
GET /users?name=kim

# 복잡한 검색 (여러 조건)
GET /users/search?name=kim&age_min=20&age_max=30

# 매우 복잡한 검색 (본문이 필요한 경우)
POST /users/search

복잡한 검색을 POST로 처리하는 것은 REST 원칙에서 벗어나지만, URL 길이 제한과 실용성 때문에 널리 쓰입니다. Elasticsearch도 이 방식을 사용합니다.


HTTP 메서드와 멱등성

멱등성(Idempotency)이란?

같은 요청을 1번 보내든 100번 보내든 결과가 동일한 성질입니다. 네트워크 오류 시 재시도할 수 있느냐의 기준이 됩니다.

메서드용도멱등안전요청 본문
GET조회OO없음
POST생성XX있음
PUT전체 교체OX있음
PATCH부분 수정X있음
DELETE삭제OX없음
  • ** 안전(Safe)**: 서버 상태를 변경하지 않음 (GET, HEAD, OPTIONS)
  • ** 멱등(Idempotent)**: 같은 요청 반복 시 결과가 동일 (GET, PUT, DELETE)
  • **PATCH가 비멱등인 이유 **: {"op": "increment", "path": "/count", "value": 1} 같은 연산은 호출할 때마다 값이 변합니다

PUT vs PATCH

PLAINTEXT
# PUT — 전체 교체 (멱등)
PUT /users/1
{
  "name": "김철수",
  "email": "kim@example.com",
  "age": 30
}
# → age를 빠뜨리면 age가 null이 됨

# PATCH — 부분 수정 (구현에 따라 비멱등)
PATCH /users/1
{
  "age": 31
}
# → age만 변경, 나머지 필드 유지

공부하다 보니 PUT과 POST의 차이보다 PUT과 PATCH의 차이에서 더 많이 헷갈렸습니다. 핵심은 PUT은 "이걸로 통째로 바꿔"이고, PATCH는 "이 부분만 고쳐"입니다.


상태 코드 정리

2xx — 성공

코드의미사용 상황
200OK일반적인 성공 (GET, PUT)
201Created리소스 생성 성공 (POST)
204No Content성공했지만 응답 본문 없음 (DELETE)

3xx — 리다이렉션

코드의미사용 상황
301Moved PermanentlyURI가 영구적으로 변경
302Found일시적 리다이렉션
304Not Modified캐시 유효 (ETag/Last-Modified)

4xx — 클라이언트 오류

코드의미사용 상황
400Bad Request요청 형식 오류, 유효성 검증 실패
401Unauthorized인증 필요 (토큰 없음/만료)
403Forbidden인증은 됐지만 권한 없음
404Not Found리소스 없음
405Method Not Allowed지원하지 않는 HTTP 메서드
409Conflict리소스 충돌 (중복 생성 등)
429Too Many RequestsRate Limit 초과

5xx — 서버 오류

코드의미사용 상황
500Internal Server Error서버 내부 오류
502Bad Gateway업스트림 서버 오류
503Service Unavailable서버 과부하 또는 점검 중
504Gateway Timeout업스트림 서버 응답 시간 초과

401과 403의 차이: 401은 "넌 누구야?"(인증 실패), 403은 "넌 알겠는데 권한이 없어"(인가 실패). 헷갈리기 쉬운데 보안 로직에서 이 구분이 중요합니다.


HATEOAS와 Richardson Maturity Model

Richardson Maturity Model

REST API의 성숙도를 4단계로 나눈 모델입니다.

레벨설명예시
0단일 URI, 모든 것을 POST로POST /api
1리소스별 URI 분리POST /users, POST /orders
2HTTP 메서드 올바르게 활용GET /users, POST /users, DELETE /users/{id}
3HATEOAS응답에 다음 행동 링크 포함

대부분의 실무 API는 Level 2에 머물고, 그것만으로도 충분히 좋은 API입니다.

HATEOAS (Level 3)

JSON
{
  "id": 1,
  "name": "김철수",
  "status": "active",
  "_links": {
    "self": { "href": "/users/1" },
    "orders": { "href": "/users/1/orders" },
    "deactivate": { "href": "/users/1/deactivate", "method": "POST" }
  }
}

클라이언트가 하드코딩된 URL 없이 응답의 링크를 따라가며 탐색할 수 있습니다. 이론적으로 아름답지만, 구현 복잡성 대비 실용적 이점이 적어서 실무에서 완전히 구현하는 경우는 드뭅니다.


API 버저닝

API가 변경될 때 기존 클라이언트를 깨뜨리지 않으려면 버저닝이 필요합니다.

방식예시장점단점
URI/v1/users직관적, 캐시 분리 용이URI가 리소스가 아닌 버전을 포함
헤더X-API-Version: 1URI가 깔끔테스트하기 불편
Content NegotiationAccept: application/vnd.api+json;version=1HTTP 표준에 가까움복잡하고 인지도 낮음

** 실무 권장 **: URI 방식 (/v1/users)이 가장 널리 쓰입니다. curl이나 브라우저에서 바로 테스트할 수 있고, CDN 캐시 키가 자연스럽게 분리됩니다.


정리

  • REST는 프로토콜이 아니라 제약 조건의 집합 — 완벽한 REST보다 실용적인 Level 2가 현실적
  • URI는 명사 + 복수형 + 소문자, 행위는 HTTP 메서드로 표현
  • 멱등성은 재시도 가능 여부를 결정 — GET/PUT/DELETE는 멱등, POST는 비멱등
  • 상태 코드는 클라이언트에게 보내는 "결과 요약" — 올바른 코드 사용이 API 품질의 기본
  • 버저닝은 URI 방식이 가장 실용적 — /v1/users
댓글 로딩 중...