"RESTful하게 설계했다"고 하는데, REST의 기준이 뭐고 어디까지 지켜야 하는 걸까요?

이게 뭔가요?

API(Application Programming Interface)는 시스템 간 통신 규약이고, REST(Representational State Transfer)는 웹 API를 설계하는 아키텍처 스타일입니다. "좋은 API"란 사용하기 쉽고, 일관되며, 확장 가능한 API를 말합니다.

왜 필요한가요?

  • API는 한 번 공개하면 바꾸기가 매우 어려움 (하위 호환성)
  • 잘 설계된 API는 문서를 보지 않아도 예측 가능
  • 나쁜 API는 클라이언트 개발자의 생산성을 크게 떨어뜨림

Richardson 성숙도 모델

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

Level 0 — The Swamp of POX

PLAINTEXT
모든 요청이 하나의 URL, 하나의 HTTP 메서드(POST)

POST /api
{
  "action": "getUser",
  "userId": 1
}

POST /api
{
  "action": "createOrder",
  "productId": 5,
  "quantity": 2
}

HTTP를 단순한 전송 수단으로만 사용합니다. RPC 스타일입니다.

Level 1 — Resources

PLAINTEXT
리소스별로 URL을 분리

POST /users/1       → 사용자 조회
POST /orders        → 주문 생성
POST /products/5    → 상품 조회

URL에 리소스 개념이 등장하지만, 여전히 HTTP 메서드를 구분하지 않습니다.

Level 2 — HTTP Verbs

PLAINTEXT
HTTP 메서드를 올바르게 사용

GET    /users/1         → 사용자 조회
POST   /users           → 사용자 생성
PUT    /users/1         → 사용자 전체 수정
PATCH  /users/1         → 사용자 부분 수정
DELETE /users/1         → 사용자 삭제

대부분의 "RESTful API"가 여기에 해당합니다. 이 정도면 충분히 좋은 API입니다.

Level 3 — HATEOAS

JSON
// 응답에 다음 가능한 액션의 링크를 포함
{
  "id": 1,
  "name": "Alice",
  "links": [
    { "rel": "self", "href": "/users/1" },
    { "rel": "orders", "href": "/users/1/orders" },
    { "rel": "update", "href": "/users/1", "method": "PUT" }
  ]
}

클라이언트가 URL을 하드코딩하지 않고, 서버 응답의 링크를 따라가며 탐색합니다. 이론적으로 가장 이상적이지만, 현실에서는 Level 2로 충분한 경우가 많습니다.

RESTful API 설계 규칙

URL 네이밍

PLAINTEXT
✓ 좋은 예:
GET  /users                    → 사용자 목록
GET  /users/1                  → 특정 사용자
GET  /users/1/orders           → 사용자의 주문 목록
GET  /users/1/orders/5         → 사용자의 특정 주문
POST /users/1/orders           → 사용자의 주문 생성

✗ 나쁜 예:
GET  /getUser?id=1             → 동사 사용
GET  /user/1                   → 단수형
POST /users/1/createOrder      → URL에 동사
GET  /Users/1                  → 대문자

핵심 규칙:

  • 명사 복수형 사용 (/users, /orders)
  • 소문자 + 하이픈 (/order-items, not /orderItems)
  • 계층 관계 는 /로 표현 (/users/1/orders)
  • 동사는 HTTP 메서드 로 표현 (URL에 동사 넣지 않기)

HTTP 메서드 의미

메서드의미멱등성안전성
GET조회멱등안전
POST생성비멱등비안전
PUT전체 교체멱등비안전
PATCH부분 수정비멱등비안전
DELETE삭제멱등비안전

멱등(Idempotent): 같은 요청을 여러 번 보내도 결과가 동일 안전(Safe): 서버 상태를 변경하지 않음

상태 코드 사용

PLAINTEXT
2xx 성공:
  200 OK           → 일반적인 성공
  201 Created      → 리소스 생성 성공 (POST)
  204 No Content   → 성공, 응답 본문 없음 (DELETE)

4xx 클라이언트 에러:
  400 Bad Request  → 잘못된 요청 (유효성 검증 실패)
  401 Unauthorized → 인증 필요
  403 Forbidden    → 인증됐지만 권한 없음
  404 Not Found    → 리소스 없음
  409 Conflict     → 충돌 (중복 등록 등)
  422 Unprocessable → 문법은 맞지만 처리 불가

5xx 서버 에러:
  500 Internal Server Error → 서버 내부 오류
  503 Service Unavailable   → 서비스 일시 중단

에러 응답 형식

JSON
// 일관된 에러 응답 구조
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "입력값이 유효하지 않습니다",
    "details": [
      {
        "field": "email",
        "message": "올바른 이메일 형식이 아닙니다"
      },
      {
        "field": "name",
        "message": "이름은 2자 이상이어야 합니다"
      }
    ]
  }
}

페이지네이션

PLAINTEXT
커서 기반 (추천):
GET /posts?cursor=eyJpZCI6MTAwfQ&limit=20

오프셋 기반 (간단하지만 대용량에서 느림):
GET /posts?page=3&size=20

응답:
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTIwfQ",
    "has_next": true,
    "total_count": 1500
  }
}

필터링, 정렬, 필드 선택

PLAINTEXT
필터링:
GET /products?category=electronics&min_price=10000

정렬:
GET /products?sort=-price,name    (price 내림차순, name 오름차순)

필드 선택:
GET /users/1?fields=id,name,email  (필요한 필드만 반환)

REST vs GraphQL

PLAINTEXT
REST:
GET /users/1           → 사용자 전체 정보 반환 (Over-fetching)
GET /users/1/orders    → 별도 요청 필요 (Under-fetching)

GraphQL:
POST /graphql
{
  query {
    user(id: 1) {
      name
      email
      orders(first: 5) {
        total
        status
      }
    }
  }
}
→ 한 번의 요청으로 필요한 것만 정확히 반환
구분RESTGraphQL
엔드포인트리소스별 여러 개하나 (/graphql)
Over/Under-fetching발생 가능클라이언트가 제어
캐싱HTTP 캐싱 쉬움캐싱 복잡
학습 곡선낮음높음
적합한 경우일반 CRUD API복잡한 데이터 관계, 모바일

자주 헷갈리는 포인트

  1. "PUT과 PATCH의 차이" — PUT은 리소스 전체를 교체하고, PATCH는 변경된 필드만 보냅니다. PUT으로 name만 보내면 나머지 필드가 null이 될 수 있습니다.

  2. "HATEOAS를 안 하면 RESTful이 아닌가" — 엄밀하게는 맞지만, Level 2(HTTP 메서드 + 리소스)로도 충분히 좋은 API를 만들 수 있습니다.

  3. "항상 200을 반환하고 body에 에러 코드를 넣는 방식" — HTTP 상태 코드를 올바르게 사용하는 것이 좋습니다. 상태 코드를 보고 에러 여부를 판단할 수 있어야 합니다.

정리

  • REST API는 리소스(명사) + HTTP 메서드(동사) + 상태 코드로 설계
  • Richardson 성숙도 Level 2(리소스 + HTTP 메서드)가 현실적인 목표
  • URL은 명사 복수형, 소문자, 계층 관계로 구성
  • 에러 응답은 일관된 구조로, 상태 코드를 올바르게 사용
  • REST가 대부분의 상황에서 적합하고, GraphQL은 복잡한 데이터 관계에서 강점
댓글 로딩 중...