REST API는 HTTP 의미를 어떻게 잘 활용하는 설계인가

Development

이전 글에서 REST API가 무엇인지, 왜 자원 중심 설계가 중요한지를 정리했습니다.

하지만 여기서 한 단계 더 들어가면 더 중요한 질문이 남습니다.

REST API가 HTTP 위에서 동작한다는 것은 알겠는데, 그렇다면 실제로 "HTTP 의미를 잘 활용한다"는 것은 무엇을 뜻할까?

이 질문에 답하지 못하면 REST API는 종종 이렇게 축소됩니다.

  • URL만 복수형으로 맞춘다
  • /users, /posts 같은 주소를 쓴다
  • JSON을 주고받는다

물론 이것도 일부는 맞습니다. 하지만 이것만으로는 RESTful하다고 보기 어렵습니다.

REST API가 진짜로 REST답기 위해서는, HTTP가 원래 제공하는 의미 체계를 API 설계에 녹여야 합니다.

이번 글에서는 그 부분을 기본기부터 실무 감각까지 연결해서 정리해보겠습니다.

한눈에 보면

먼저 짧게 정리하면 이렇습니다.

  • REST API 설계는 단순 URL 작명이 아니라 HTTP 의미를 설계에 반영하는 일입니다.
  • 메서드는 행위의 의도를 드러내고, 상태 코드는 결과의 의미를 드러냅니다.
  • 헤더는 표현 형식, 인증, 캐시, 조건부 요청 같은 중요한 계약을 담습니다.
  • URL은 기능 이름보다 자원을 드러내는 편이 좋습니다.
  • safe, idempotent, 캐시 가능성, 리다이렉트 가능성 같은 HTTP 특성을 이해해야 실무 설계가 흔들리지 않습니다.

즉, REST API에서 HTTP를 잘 쓴다는 것은 "HTTP를 전송 수단으로만 쓰지 않고, 의미 체계까지 같이 쓴다"는 뜻에 가깝습니다.

왜 HTTP 의미를 굳이 활용해야 할까?

처음에는 이렇게 생각할 수 있습니다.

  • "어차피 클라이언트와 서버가 약속만 하면 되지 않나?"
  • "전부 POST로 받고 body에 action을 넣어도 동작은 하잖아?"

맞습니다. 기술적으로는 동작할 수 있습니다.

하지만 그렇게 설계하면 이미 HTTP가 제공하는 수많은 공통 의미를 버리게 됩니다.

예를 들어 HTTP는 이미:

  • 조회와 변경을 구분하는 메서드 의미
  • 성공과 실패를 구분하는 상태 코드 체계
  • 캐시 재사용 규칙
  • 조건부 요청 규칙
  • 인증과 상태 유지에 쓰이는 헤더 구조

를 제공합니다.

그런데 API가 이 의미를 무시하면 어떤 문제가 생길까요?

  • 클라이언트가 엔드포인트마다 새로운 규칙을 배워야 하고
  • 캐시와 재시도 전략을 활용하기 어렵고
  • 디버깅과 운영이 어려워지고
  • 프론트와 백엔드가 같은 API를 보고도 다른 해석을 할 수 있습니다

즉, HTTP 의미를 잘 활용한다는 것은 단순 "정석"의 문제가 아니라, 시스템을 예측 가능하고 운영 가능하게 만드는 문제입니다.

첫 번째 원칙: URL은 자원을 드러내고, 메서드는 행위를 드러내야 한다

이것이 가장 기본적이면서도 가장 자주 흔들리는 원칙입니다.

예를 들어 아래 두 설계를 비교해보겠습니다.

덜 RESTful한 형태

POST /getUser
POST /createUser
POST /updateUser
POST /deleteUser

HTTP 의미를 살린 형태

GET /users/1
POST /users
PATCH /users/1
DELETE /users/1

두 번째 형태가 더 좋은 이유는 단지 보기 좋아서가 아닙니다.

  • URL은 users라는 자원을 드러내고
  • 메서드는 조회/생성/수정/삭제의 의도를 드러내며
  • 클라이언트는 문서를 보기 전에도 어느 정도 의미를 예측할 수 있습니다

즉, URL과 메서드가 각자의 역할을 나눠 가질 때 API는 훨씬 읽기 쉬워집니다.

두 번째 원칙: 메서드는 단순 문법이 아니라 계약이다

HTTP 메서드는 단순 명령어가 아닙니다.

예를 들어:

  • GET은 조회성 요청으로 기대되고
  • POST는 생성이나 비멱등적 처리에 자주 쓰이고
  • PUT은 전체 교체 의미로 자주 설명되며
  • PATCH는 부분 수정에 가깝고
  • DELETE는 삭제 의미를 가집니다

이 의미를 무시하면 API는 금방 혼란스러워집니다.

예를 들어 조회인데도 POST를 사용하면:

  • 캐시 이점을 활용하기 어렵고
  • safe한 읽기 요청이라는 감각이 흐려지고
  • 프록시, CDN, 클라이언트 라이브러리 관점에서도 의미가 약해집니다

즉, 메서드는 "서버가 알아서 처리하면 되는 값"이 아니라, 클라이언트와 서버가 공유하는 의미의 핵심입니다.

GET, POST, PUT, PATCH, DELETE를 실무적으로 어떻게 구분할까?

기본기이지만, 실무에서 정말 자주 흔들리는 부분입니다.

GET

조회에 사용합니다.

  • 목록 조회
  • 상세 조회
  • 필터링된 검색 결과 조회

예를 들어:

GET /posts
GET /posts/1
GET /posts?author=marco&status=published

GET은 가능하면 safe하고 idempotent하다는 의미를 지켜주는 편이 좋습니다.

POST

새 자원 생성이나, 비멱등적인 처리, 혹은 단순 CRUD로 딱 떨어지지 않는 액션에 자주 씁니다.

예를 들어:

POST /posts
POST /payments

특히 같은 요청을 여러 번 보내면 중복 생성이나 중복 처리 가능성이 있다면 POST 성격으로 보는 것이 자연스럽습니다.

PUT

특정 자원을 전체 교체하는 의미에 가깝습니다.

예를 들어:

PUT /users/1

보낸 표현이 그 자원의 새 전체 상태라는 해석에 더 가깝습니다.

PATCH

일부 필드만 수정하는 의미로 많이 씁니다.

예를 들어:

PATCH /users/1
Content-Type: application/json
 
{
  "nickname": "new-name"
}

실무에서는 PUT보다 PATCH를 더 자주 보기도 합니다. 특히 프론트엔드에서 부분 수정 폼과 연결하기 쉽기 때문입니다.

DELETE

자원 삭제에 씁니다.

DELETE /posts/1

중요한 것은 메서드 하나하나를 외우는 것이 아니라, 이 요청의 의미를 가장 잘 드러내는가를 기준으로 보는 것입니다.

세 번째 원칙: 상태 코드는 "성공/실패"가 아니라 결과 의미를 표현해야 한다

실무에서 REST API를 망가뜨리는 흔한 패턴 중 하나가, 모든 응답을 200 OK로 보내고 body 안에 별도 code를 넣는 방식입니다.

예를 들어:

{
  "success": false,
  "code": "USER_NOT_FOUND"
}

이런 구조 자체가 잘못은 아닙니다. 보조 정보로는 충분히 쓸 수 있습니다. 문제는 HTTP 상태 코드 의미를 거의 버려버리는 것입니다.

REST API에서 HTTP를 잘 활용한다면 상태 코드는 먼저 프로토콜 차원의 의미를 드러내야 합니다.

예를 들어:

  • 200 OK: 정상 조회/수정 성공
  • 201 Created: 자원 생성 성공
  • 204 No Content: 성공했지만 바디는 굳이 필요 없음
  • 400 Bad Request: 요청 형식 문제
  • 401 Unauthorized: 인증 문제
  • 403 Forbidden: 권한 문제
  • 404 Not Found: 자원 없음
  • 409 Conflict: 충돌
  • 422 Unprocessable Entity: 형식은 맞지만 도메인 검증 실패
  • 500 Internal Server Error: 서버 내부 문제

즉, 상태 코드는 단순 숫자가 아니라 클라이언트가 다음 행동을 결정할 수 있게 해주는 프로토콜 수준의 결과 표현입니다.

200, 201, 204를 실무에서 왜 나눠야 할까?

이 세 개는 모두 성공이라 대충 묶어도 동작은 합니다. 하지만 의미를 나누면 API가 훨씬 또렷해집니다.

200 OK

조회 성공이나 수정 후 결과 본문을 돌려줄 때 자연스럽습니다.

201 Created

새 자원이 만들어졌다는 사실 자체가 중요한 경우에 적합합니다.

예를 들어 회원 가입, 게시글 생성, 주문 생성 같은 경우입니다.

204 No Content

삭제 성공, 토글 성공, 혹은 서버가 굳이 응답 본문을 주지 않아도 되는 경우에 유용합니다.

예를 들어:

DELETE /posts/1

성공 후 클라이언트가 별도 본문 없이 상태만 알면 충분하다면 204가 더 명확할 수 있습니다.

즉, 같은 성공이라도 무슨 종류의 성공인지를 드러내는 것이 중요합니다.

400, 401, 403, 404, 409, 422는 어떻게 나눌까?

이 부분은 실무에서 정말 중요합니다. API 소비자 입장에서 모두 다르게 행동해야 하기 때문입니다.

400 Bad Request

요청 형식 자체가 잘못된 경우입니다.

  • 필수 파라미터 누락
  • JSON 구조 오류
  • 잘못된 쿼리 파라미터 형식

401 Unauthorized

인증이 안 되었거나 인증 정보가 유효하지 않을 때입니다.

프론트에서는 보통 로그인 흐름이나 토큰 재발급 흐름과 연결됩니다.

403 Forbidden

인증은 되었지만 접근 권한이 없는 경우입니다.

즉, "누구인지는 알지만 허용할 수는 없음"에 가깝습니다.

404 Not Found

요청한 자원이 존재하지 않거나 접근 가능한 범위에서 찾을 수 없을 때입니다.

409 Conflict

요청이 현재 자원 상태와 충돌할 때 유용합니다.

예를 들어:

  • 이미 처리된 주문을 다시 처리하려는 경우
  • 중복된 unique 값으로 생성하려는 경우

422 Unprocessable Entity

요청 형식은 맞지만 도메인 규칙 검증에 실패한 경우에 자주 씁니다.

예를 들어:

  • 닉네임 형식 규칙 위반
  • 재고보다 많은 수량 주문
  • 허용되지 않는 상태 전이 요청

즉, 좋은 API는 실패도 세분화해서 표현합니다. 그래야 클라이언트가 **"다시 로그인할지", "입력을 고칠지", "권한 없음 UI를 보여줄지"**를 정확히 나눌 수 있습니다.

네 번째 원칙: 헤더도 설계의 일부다

많은 경우 REST API 설계는 URL과 JSON 바디에만 관심이 쏠립니다. 하지만 헤더도 중요한 의미를 담습니다.

예를 들어:

  • Content-Type: 요청/응답 바디 형식
  • Accept: 클라이언트가 기대하는 표현 형식
  • Authorization: 인증 정보
  • Cache-Control: 캐시 정책
  • ETag, Last-Modified: 조건부 요청과 캐시 검증
  • Location: 생성된 자원 위치

즉, 헤더는 부가 옵션이 아니라 HTTP 의미 체계를 완성하는 중요한 계약면입니다.

Location 헤더는 언제 중요할까?

예를 들어 새 자원을 생성한 뒤:

HTTP/1.1 201 Created
Location: /posts/123

처럼 생성된 자원의 위치를 알려줄 수 있습니다.

이런 방식은 "무엇이 생성되었는가"를 더 명확하게 표현합니다.

다섯 번째 원칙: 캐시 가능성은 설계에서 미리 생각해야 한다

HTTP를 잘 활용하는 REST API는 캐시를 단순 부가 기능으로 보지 않습니다.

예를 들어:

  • 공지사항 목록
  • 공개 상품 상세
  • 변경 빈도가 낮은 설정 정보

같은 것은 충분히 캐시 가능한 후보입니다.

이때:

  • Cache-Control
  • ETag
  • Last-Modified

같은 헤더를 활용하면 브라우저나 CDN, 중간 프록시가 응답을 더 효율적으로 다룰 수 있습니다.

즉, 캐시는 프론트 성능 최적화의 문제가 아니라, HTTP 의미를 API 설계에 잘 반영하고 있는가의 문제이기도 합니다.

여섯 번째 원칙: safe와 idempotent를 설계에 반영해야 한다

REST API가 HTTP 의미를 잘 활용하려면 safe, idempotent 개념도 중요합니다.

safe

safe한 요청은 서버 자원을 바꾸지 않는 조회성 요청입니다.

대표적으로 GET은 safe하다고 기대됩니다.

즉, 조회를 위해 새 자원을 만들거나 상태를 변경하면 안 됩니다.

idempotent

같은 요청을 여러 번 보내도 최종 결과 의미가 같아야 한다는 개념입니다.

예를 들어:

  • 같은 PUT 요청 반복
  • 같은 DELETE 요청 반복

은 보통 idempotent하게 설계하려고 합니다.

왜 실무에서 중요할까?

네트워크는 언제든 실패할 수 있습니다.

  • 모바일 환경에서 재시도될 수 있고
  • 프록시가 재전송할 수 있고
  • 사용자가 버튼을 여러 번 누를 수 있습니다

이때 API 의미가 불안정하면:

  • 중복 결제
  • 중복 주문
  • 중복 생성

같은 문제가 발생할 수 있습니다.

즉, safe와 idempotent는 이론 용어가 아니라 장애 상황에서 시스템을 안전하게 만드는 설계 감각입니다.

일곱 번째 원칙: 쿼리 파라미터는 자원 검색과 조건 표현에 쓰는 편이 자연스럽다

REST API에서 URL 설계를 할 때 자주 헷갈리는 부분입니다.

예를 들어:

GET /posts?page=2&limit=20&status=published&author=marco

이런 구조는 매우 자연스럽습니다.

왜냐하면:

  • 기본 자원은 posts이고
  • 쿼리 파라미터는 그 자원을 어떤 조건으로 조회할지 설명하기 때문입니다

즉:

  • path는 자원 식별
  • query는 필터링, 정렬, 페이징, 검색 조건

으로 나누면 읽기 쉬워집니다.

여덟 번째 원칙: 관계가 있는 자원은 URL에서도 관계가 보이면 좋다

자원 간 관계는 REST API에서 중요한 설계 포인트입니다.

예를 들어:

GET /users/1/posts
GET /posts/1/comments

이런 구조는:

  • 어떤 자원의 하위 컬렉션인지
  • 어떤 맥락의 조회인지

를 잘 드러냅니다.

물론 무조건 중첩 URL이 정답은 아닙니다. 너무 깊어지면 오히려 복잡해질 수도 있습니다.

예를 들어:

/companies/1/departments/2/teams/3/members/4/posts/5/comments/6

처럼 지나치게 깊어지면 사용성과 유지보수성이 떨어집니다.

즉, 관계를 드러내되, 과한 중첩보다는 읽기 쉬운 수준의 자원 맥락 표현이 더 중요합니다.

아홉 번째 원칙: 액션성 도메인은 무조건 피할 게 아니라, 의미를 잘 드러내야 한다

실무에서는 순수 CRUD로 설명하기 어려운 동작이 많습니다.

  • 결제 승인
  • 주문 취소
  • 이메일 재전송
  • 비밀번호 재설정 요청

이런 동작을 억지로 CRUD에만 맞추려 하면 오히려 더 어색해질 수 있습니다.

예를 들어 주문 취소는 이렇게 볼 수 있습니다.

POST /orders/1/cancellations

이 설계는 "취소"를 하나의 새로운 하위 자원으로 보는 방식입니다.

혹은 도메인에 따라:

POST /orders/1/cancel

처럼 액션 엔드포인트를 둘 수도 있습니다.

중요한 것은 "동사 URL은 절대 금지" 같은 기계적 규칙이 아니라, 도메인 의미와 HTTP 의미를 함께 고려해 가장 읽기 쉬운 설계를 택하는 것입니다.

즉, 실무에서는 완벽주의보다 설명 가능성과 일관성이 중요합니다.

열 번째 원칙: 에러 응답도 계약이다

좋은 REST API는 성공 응답만 예쁘게 만들지 않습니다.

실패했을 때도 클라이언트가 이해할 수 있어야 합니다.

예를 들어:

{
  "message": "nickname is already taken",
  "code": "NICKNAME_ALREADY_EXISTS",
  "fieldErrors": {
    "nickname": "already taken"
  }
}

같은 구조는 프론트엔드가:

  • 토스트를 보여줄지
  • 폼 에러를 필드에 매핑할지
  • 재시도 버튼을 보여줄지

를 정하는 데 도움이 됩니다.

즉, 상태 코드는 프로토콜 수준의 의미이고, 에러 바디는 도메인 수준의 추가 설명입니다. 둘은 대체 관계가 아니라 보완 관계에 가깝습니다.

프론트엔드에서는 이런 REST API가 왜 중요한가?

프론트엔드 관점에서 HTTP 의미를 잘 활용한 REST API는 정말 큰 차이를 만듭니다.

1. UI 분기가 쉬워진다

  • 401이면 로그인 유도
  • 403이면 권한 없음 화면
  • 404면 존재하지 않음 화면
  • 422면 입력 검증 에러 표시

처럼 상태 코드만으로도 기본 분기가 쉬워집니다.

2. 캐시 전략을 세우기 쉬워진다

조회 API가 GET이고 캐시 정책이 분명하면 브라우저 캐시, SWR, React Query 같은 도구와도 자연스럽게 연결됩니다.

3. 재시도 전략을 세우기 쉬워진다

멱등성 성격이 분명하면 네트워크 에러 시 어떤 요청을 재시도해도 괜찮은지 판단하기 쉬워집니다.

즉, 좋은 REST API는 백엔드만 편한 것이 아니라, 프론트엔드의 상태 관리와 예외 처리, 사용자 경험까지 좋게 만듭니다.

백엔드에서는 이런 REST API가 왜 중요한가?

백엔드 입장에서도 의미가 큽니다.

  • API 문서가 짧아져도 예측 가능성이 높고
  • 신규 개발자가 규칙을 빨리 이해할 수 있으며
  • 운영 중 디버깅 포인트가 분명해지고
  • 프록시, CDN, 캐시, 인증 계층과도 잘 맞습니다

즉, HTTP 의미를 잘 활용한 REST API는 단순 "스타일"이 아니라 팀 전체의 운영 비용을 낮추는 인터페이스 설계입니다.

자주 하는 실수

실무에서 자주 보는 실수를 정리하면 아래와 같습니다.

1. 모든 요청을 POST로 만든다

동작은 되지만 HTTP 의미를 버리게 됩니다.

2. 모든 성공을 200, 모든 실패를 400으로 보낸다

클라이언트가 다음 행동을 세밀하게 나누기 어려워집니다.

3. URL에 동사를 과하게 넣는다

메서드와 역할이 겹치고 자원 구조가 흐려집니다.

4. 캐시와 멱등성을 설계 단계에서 아예 고려하지 않는다

처음에는 문제없어 보여도 트래픽과 장애 상황에서 큰 차이를 만듭니다.

5. 에러 바디를 일관성 없이 만든다

프론트에서 예외 처리가 지저분해지고 재사용성이 떨어집니다.

실무적으로는 어디까지 지키면 좋을까?

이 질문도 중요합니다.

현실의 시스템은 늘 깔끔하지 않습니다.

  • 레거시 호환이 필요할 수 있고
  • 외부 API 제약이 있을 수 있고
  • 파일 업로드, 검색, 배치 실행, 결제 같은 도메인은 순수 CRUD로 설명하기 어렵기도 합니다

그래서 실무에서는 "교과서 REST를 100% 달성했는가?"보다 아래가 더 중요합니다.

  • 자원이 명확한가
  • 메서드 의미를 억지로 깨지 않는가
  • 상태 코드가 일관적인가
  • 에러 응답이 예측 가능한가
  • 캐시와 멱등성을 고려했는가

즉, 실무적으로 좋은 REST API는 완벽한 순수주의보다 HTTP 의미를 꾸준히 존중하는 일관된 설계에 더 가깝습니다.

같이 보면 좋은 글

결론

REST API가 HTTP 위에서 동작한다는 말의 진짜 의미는, 단지 HTTP를 "운반 수단"으로 쓴다는 뜻이 아닙니다. 더 본질적으로는 HTTP가 원래 갖고 있는 메서드, 상태 코드, 헤더, 캐시, 멱등성의 의미를 설계에 반영한다는 뜻에 가깝습니다.

짧게 정리하면:

  • URL은 자원을 드러내고
  • 메서드는 행위를 드러내며
  • 상태 코드는 결과 의미를 드러내고
  • 헤더와 캐시, 멱등성까지 함께 설계해야 REST API가 더 REST다워집니다

결국 좋은 REST API 설계는 새로운 규칙을 많이 만드는 것이 아니라, 웹이 이미 제공하고 있는 언어를 최대한 잘 활용하는 것에 가깝습니다.