본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 27. 10:34

AI조차 할 수 없는 5가지, REST API

요약

REST API의 창시자인 Roy Fielding의 관점을 통해 현재 업계에서 오용되고 있는 REST의 개념을 바로잡습니다. 진정한 REST의 정의와 함께 실무에서 반드시 고려해야 할 API 설계의 핵심 요소들을 다룹니다.

핵심 포인트

  • Roy Fielding이 정의한 REST의 6가지 제약 조건 이해
  • 단순 HTTP+JSON 호출과 진정한 REST의 차이점 구분
  • 상태 코드, 멱등성, 페이지네이션 등 실무적 API 설계 원칙

Roy Fielding이 당신의 "REST API"에 대해 한마디 하고 싶어 합니다

2008년 10월, 2000년 박사 학위 논문에서 말 그대로 REST를 발명한 인물이자 HTTP의 공동 저자인 Roy Fielding은 다음과 같은 문장으로 블로그 포스트를 시작했습니다:

저는 어떤 HTTP 기반 인터페이스든 REST API라고 부르는 수많은 사람 때문에 좌절감을 느끼고 있습니다.

그 후 그는 약 천 단어에 걸쳐, 실제로는 REST가 아닌 것들을 REST라고 부르는 업계 전체를 비판했습니다. 18년이 지난 지금까지도 모두가 인용하는 문구는 다음과 같습니다:

그것은 RPC입니다. RPC라고 외치고 있습니다. 결합도(Coupling)가 너무 심해서 X 등급을 받아야 할 수준입니다.

독자 여러분, 저 또한 커리어 동안 REST API라고 불렀지만 Fielding의 정의에 따르면 REST API가 아니었던 수십 개의 API를 구축했습니다. 여러분도 아마 그럴 것입니다. 여러분이 사용해 본 거의 모든 "REST API"는 발명가 본인의 기준에 따르면 REST가 아닙니다. 그리고 업계는 집단적으로 그것이 괜찮다고 결정했으며, 우리는 어쨌든 REST라는 단어를 사용합니다. Fielding은 아마도 캘리포니아의 사무실에서 여전히 그 사실에 대해 심술이 나 있을 것입니다.

이 글은 REST가 실제로 무엇인지, 사람들이 2026년에 REST라고 말할 때 의미하는 바가 무엇인지, 그 둘의 차이점, 그리고 여러분의 API를 REST라고 부르든, REST-ish라고 부르든, HTTP+JSON이라고 부르든 상관없이 실제로 제대로 구현해야 하는 일상적인 실무 세부 사항들 — 상태 코드(Status codes), 멱등성 (Idempotency), 페이지네이션 (Pagination), 에러 형식 (Error formats), 버전 관리 (Versioning) — 에 대한 긴 버전의 설명입니다. 커피를 준비하세요. 다뤄야 할 내용이 많습니다.

아무도 읽지 않은 논문

Roy Thomas Fielding의 박사 학위 논문인 _Architectural Styles and the Design of Network-based Software Architectures_는 2000년 UC Irvine에서 발표되었습니다. 이 논문은 180페이지에 달합니다. 제5장의 제목은 "Representational State Transfer (REST)"이며, 여기서 이 용어가 유래되었습니다. 만약 이 논문을 읽어본 적이 없다면 괜찮습니다. 거의 아무도 읽지 않았으니까요. 하지만 이 논문이 존재한다는 사실은 알고 있어야 합니다. 왜냐하면 어떤 것이 "진정한 REST"인지에 대한 모든 논쟁은 결국 이 문서로 돌아오기 때문입니다.

그 배경이 중요합니다. Fielding은 IETF에서 HTTP/1.1 명세와 URI 표준을 공동 작성하는 동시에 이 논문을 작성했습니다. 그는 외부에서 웹에 대해 이론을 세우고 있었던 것이 아닙니다. 그는 웹이 어떻게 작동해야 하는지에 대해 글을 쓰는 동시에 웹을 직접 _구축(building)_하고 있었습니다. 이것이 바로 REST가 권위를 갖는 이유입니다. 이 스타일을 정의한 사람이 바로 그 스타일이 실행되는 프로토콜을 정의하는 사람들 중 한 명이었기 때문입니다.

Fielding에 따르면, REST는 6가지 제약 조건(constraints)으로 정의됩니다. 5가지는 필수이며, 1가지는 선택 사항입니다:

  1. 클라이언트-서버 (Client–Server) — 관심사의 분리 (separation of concerns). 클라이언트와 서버는 독립적으로 진화합니다.
  2. 무상태성 (Stateless) — 모든 요청은 이를 이해하는 데 필요한 모든 정보를 포함합니다. 서버는 클라이언트의 세션 상태를 저장하지 않습니다.
  3. 캐시 가능성 (Cacheable) — 응답은 스스로 캐시 가능 여부를 선언해야 합니다.
  4. 균일한 인터페이스 (Uniform Interface) — 이것이 핵심입니다. 나중에 자세히 다루겠습니다.
  5. 계층화된 시스템 (Layered System) — 클라이언트는 자신이 원본 서버(origin server)와 통신하고 있는지 아니면 중간 매개체(intermediary)와 통신하고 있는지 알 수 없습니다.
  6. 온디맨드 코드 (Code on Demand, 선택 사항) — 서버는 클라이언트를 확장하기 위해 실행 가능한 코드를 보낼 수 있습니다.

네 번째 제약 조건인 균일한 인터페이스에는 4가지 하위 제약 조건이 있으며, 바로 이 지점에서 업계가 조용히 규칙 준수를 멈췄습니다:

  • 리소스 식별 (Identification of resources)
  • 표현(representations)을 통한 리소스 조작 (Manipulation of resources through representations)
  • 자기 설명적 메시지 (Self-descriptive messages)
  • 애플리케이션 상태의 엔진으로서의 하이퍼미디어 (Hypermedia as the engine of application state)

마지막 하위 제약 조건에는 약어가 있습니다: HATEOAS. 이는 REST API의 응답이 클라이언트에게 다음에 무엇을 할 수 있는지 알려주는 링크 — 하이퍼미디어(hypermedia) — 를 포함해야 함을 의미합니다. 웹페이지처럼 말이죠. 웹사이트를 탐색하기 위해 URL을 암기하지 않고 링크를 클릭하는 것과 같습니다. 진정한 REST API도 같은 방식으로 작동합니다. 클라이언트는 하나의 진입점에서 시작하여 링크가 포함된 응답을 받고, 그 링크들을 따라가며 애플리케이션을 탐색합니다.

여러분이 사용해 본 API 중 이런 방식으로 작동하는 것은 거의 없습니다.

와인처럼 숙성된 2008년의 일침

2008년 무렵, "REST"는 업계의 유행어가 되었습니다. 모든 기업이 "REST API"를 출시하고 있었는데, 이는 단순히 JSON을 반환하는 엔드포인트(endpoint)를 가지고 있다는 의미였습니다. Fielding은 몇 년 동안 이런 현상을 지켜보다가 결국 폭발했습니다. 2008년 10월 20일에 작성된 "REST API는 하이퍼텍스트 기반이어야 한다(REST APIs must be hypertext-driven)"라는 제목의 포스트는, 그 이후 트위터에서 발생한 모든 "사실 그건 진짜 REST가 아니에요"라는 논쟁의 근간이 되는 문서가 되었습니다.

주요 발췌문은 다음과 같습니다:

애플리케이션 상태(및 그에 따른 API)의 엔진이 하이퍼텍스트(hypertext)에 의해 구동되지 않는다면, 그것은 RESTful할 수 없으며 REST API가 될 수 없습니다. 끝.

댓글에서 그는 자신의 주장을 더욱 강화했습니다:

진정으로 RESTful한 API는 하이퍼텍스트처럼 보입니다.

그리고 제가 가장 자주 생각하게 되는 문장 역시 댓글에 있었습니다:

REST는 수십 년 단위의 소프트웨어 설계입니다. 모든 세부 사항은 소프트웨어의 수명과 독립적인 진화를 촉진하기 위해 의도되었습니다. 많은 제약 사항이 단기적인 효율성과 정면으로 배치됩니다.

제 생각에 마지막 문장이 바로 HATEOAS가 패배한 진짜 이유입니다. 마감 압박에 시달리는 엔지니어들은 수십 년을 내다보고 최적화하지 않습니다. 그들은 다음 스프린트(sprint)를 위해 최적화합니다. HATEOAS는 즉각적인 복잡성을 대가로 매우 긴 시간 지평(time horizon) 동안 API의 회복탄력성을 높여줍니다. 그래서 우리 모두는 그냥 레벨 2(Level 2)를 출시해 버린 것입니다.

리처드슨 성숙도 모델 (The Richardson Maturity Model)

실무에서 REST를 생각하는 더 나은 방법은 2008년—공교롭게도 Fielding이 분노를 터뜨린 것과 같은 해—에 리처드슨 성숙도 모델(Richardson Maturity Model)을 발표한 Leonard Richardson으로부터 나왔습니다. Martin Fowler가 자신의 블로그에 이를 정리하여 올렸고, 이는 업계에 자리 잡았습니다. 이는 업계에서 가장 유용한 진단 도구입니다.

레벨 0 — "POX의 늪(The Swamp of POX)." 하나의 URL만 존재합니다. 모든 것이 POST 방식입니다. 요청 본문(request body)이 무엇이 일어날지를 결정합니다. 이것은 SOAP입니다. 예를 들어 /api/endpoint{ "action": "getUser", "id": 123 }를 받는 식입니다. 이것이 최하위 단계입니다.

레벨 1 — 리소스(Resources). 서로 다른 것들에 대해 별도의 URI를 가집니다. /users, /orders/42와 같이 말이죠. 여전히 모든 것을 POST로 처리할 수도 있지만, 적어도 URL이 리소스를 식별합니다.

Level 2 — HTTP Verbs (HTTP 동사). 읽기에는 GET을, 생성에는 POST를, 교체에는 PUT을, 부분 업데이트에는 PATCH를, 삭제에는 DELETE를 사용합니다. 성공 시에는 200, 찾을 수 없을 때는 404, 오류가 발생했을 때는 500과 같은 적절한 상태 코드 (Status Codes)를 반환합니다. 이것이 Stripe가 작동하는 방식입니다. 이것이 GitHub가 작동하는 방식입니다. 당신이 사용해 온 모든 "REST API"가 작동하는 방식입니다. 이것이 업계의 99%가 "REST"라고 부르는 것입니다.

Level 3 — Hypermedia controls (HATEOAS). 모든 응답에는 클라이언트에게 다음에 가능한 작업이 무엇인지 알려주는 링크가 포함되어 있습니다. 클라이언트는 URL을 기억해서 구성하는 것이 아니라, 링크를 따라가며 API를 탐색합니다.

Fielding의 2008년 포스트는 기본적으로 _Level 3 미만은 REST라고 불려서는 안 된다_는 항의입니다. Richardson의 모델은 좀 더 외교적입니다. 이는 당신에게 사다리를 제공하여 현재 위치를 파악하게 해주며, 끝까지 올라가라고 강요하지 않습니다.

업계는 Level 2에서 멈췄고, 그것을 REST라고 이름을 붙였습니다. 그것이 이야기의 전부입니다. 이 이후의 모든 것은 단순히 Level 2를 잘 수행하기 위한 엔지니어링 세부 사항일 뿐입니다.

2026년에 "REST API"가 실제로 의미하는 것

현업 개발자의 솔직한 정의를 말씀드리겠습니다. 2026년의 "REST API"란 다음과 같은 API를 말합니다:

  • HTTP를 전송 프로토콜 (Transport)로 사용함
  • JSON을 반환함 (이제 XML은 거의 사용되지 않음)
  • 리소스 지향적 (Resource-oriented) URL을 가짐 — 동사가 아닌 명사 사용 (/getUser?id=123이 아닌 /users/123)
  • HTTP 동사 (HTTP Verbs)를 올바르게 사용함 (GET은 읽기, POST는 생성 등)
  • 거짓을 말하지 않는 표준 HTTP 상태 코드 (Status Codes)를 반환함
  • 문서화되어 있으며, 이상적으로는 OpenAPI를 따름

그게 전부입니다. 이것이 99%의 엔지니어가 머릿속에 담고 있는 정의이며, 99%의 실제 API가 충족하는 정의입니다. 엄격히 말하면, 이는 Richardson 척도의 Level 2입니다. Fielding의 기준에 따르면 엄격히 말해 REST가 아닙니다. 하지만 아무도 신경 쓰지 않습니다.

우리가 여전히 이를 REST라고 부르는 이유는 다른 대안적인 명칭들이 더 나쁘기 때문입니다. "HTTP+JSON API"는 정확하지만 투박합니다. "REST-ish (REST스러운)"는 솔직하지만 사과하는 듯한 느낌을 줍니다. "Web API"는 너무 광범위합니다. "RESTful"은 눈가림용(fig leaf)에 불과합니다. 이는 "우리가 완전한 REST가 아니라는 것을 알고 있지만, 부분 점수라도 주세요"라는 신호를 보냅니다. 그래서 우리는 그냥 REST라고 말하고, 모두가 그 의미를 이해하며, 다음 단계로 넘어갑니다.

HTTP Verbs(HTTP 메서드)와 멱등성(Idempotency) 종교

여러분이 실제로 필요로 하는 동사(verb) 치트 시트입니다:

  • GET — 리소스를 읽습니다. 멱등(Idempotent)합니다. 안전(Safe)합니다. 캐싱(Cacheable) 가능합니다. 여기에 부수 효과(side effects)를 두지 마세요. 네, 여러분의 트래킹 픽셀이 그렇게 한다는 건 알고 있습니다. 그래도 하지 마세요.
  • POST — 리소스를 생성하거나, "어떤 동작을 수행"합니다. 멱등하지 않습니다.
  • PUT — 리소스를 완전히 교체합니다. 멱등합니다.
  • PATCH — 리소스를 부분적으로 업데이트합니다. 설계 방식에 따라 멱등할 수도 있지만, 대개 그렇지 않습니다.
  • DELETE — 리소스를 삭제합니다. 멱등합니다.

"멱등(idempotent)"이라는 단어는 마치 모두가 그 의미에 동의하는 것처럼 남발되곤 합니다. 그러니 제가 명확하게 정의하겠습니다: 어떤 요청을 N번 수행했을 때의 서버 상태가 요청을 한 번 수행했을 때의 상태와 같다면, 그 요청은 멱등합니다. ID가 42인 사용자를 10번 삭제해도 한 번 삭제했을 때와 동일한 상태가 남습니다. 즉, 사용자는 사라진 상태입니다. 따라서 DELETE는 멱등합니다. "새 주문 생성"을 POST로 10번 요청하면 10개의 주문이 생성됩니다. 따라서 POST는 멱등하지 않습니다.

이것이 왜 중요할까요? 네트워크는 신뢰할 수 없기 때문입니다. 클라이언트가 요청을 보냈는데 네트워크 문제로 응답이 유실되면, 클라이언트는 요청이 성공했는지 여부를 알 수 없습니다. 만약 요청이 멱등하다면 재시도(retry)를 해도 안전합니다. 하지만 멱등하지 않다면, 재시도로 인해 고객에게 비용이 두 번 청구될 수도 있습니다.

Stripe는 이에 대한 전형적인 해결책을 가지고 있으며, 이는 암기할 가치가 있습니다. 그들은 Idempotency-Key라고 불리는 HTTP 헤더를 사용합니다. 클라이언트는 고유한 키(보통 UUID)를 생성하여 요청과 함께 보냅니다:

curl https://api.stripe.com/v1/charges \
  -u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
  -H "Idempotency-Key: AGJ6FJMkGQIpHUTX" \
...

서버는 해당 키(key) 아래에 첫 번째 요청의 결과를 일정 기간(Stripe v1의 경우 24시간, v2의 경우 30일) 동안 저장합니다. 만약 동일한 키가 다시 들어오면, 서버는 고객에게 다시 결제하는 대신 캐시된 결과를 반환합니다. 클라이언트는 중복 결제에 대한 걱정 없이 원하는 만큼 여러 번 재시도(retry)할 수 있습니다.

돈과 관련된 무엇인가를 구축하고 있다면, Stripe 엔지니어링 블로그에 게시된 Brandur Leach의 "Designing robust and predictable APIs with idempotency"는 필독서입니다. 이 패턴은 결제뿐만 아니라 모든 비멱등적(non-idempotent) 작업에 적용될 수 있습니다.

실제로 중요한 상태 코드 (Status Codes)

60여 개의 HTTP 상태 코드(status codes)가 존재하지만, 여러분은 그중 약 15개 정도만 알면 됩니다. 실무에서 사용하는 세트는 다음과 같습니다:

200 OK — 성공했으며, 본문(body)이 포함됨. GET, PUT, PATCH 응답의 기본값입니다.

201 Created — 새로운 리소스(resource)가 생성됨. 새로운 리소스를 가리키는 Location 헤더를 포함해야 합니다. 무언가를 생성하는 POST 요청에 유용합니다.

204 No Content — 성공했으나, 의도적으로 본문이 비어 있음. 명세(spec)에 따르면 204 응답에는 본문을 보낼 수 없습니다. DELETE 요청이나, 업데이트된 리소스를 다시 반환할 필요가 없는 PUT 요청에 자연스러운 답변입니다. DELETE가 200(본문에 삭제된 엔티티 포함)을 반환해야 하는지, 아니면 204(아무것도 없음)를 반환해야 하는지에 대해 끊임없는 논쟁이 있습니다. 둘 다 타당한 근거가 있습니다. 하나를 선택하고 일관성을 유지하세요.

301 Moved Permanently — URL 버전 관리 마이그레이션에 유용합니다. 브라우저가 이를 공격적으로 캐싱하므로 주의해야 합니다.

400 Bad Request — 클라이언트가 잘못된 데이터를 보냄. 형식이 잘못된 JSON, 필수 필드 누락 등과 같은 경우입니다.

401 Unauthorized — 인증(authenticated)되지 않음. 명세에서는 이를 "Unauthorized"라고 부르지만, 실제 의미는 "Unauthenticated(미인증)"입니다. 이름이 좋지 않기 때문에 모두가 이를 혼동합니다.

403 Forbidden — 인증은 되었으나, 해당 작업을 수행할 권한(permission)이 없음. HTTP의 "너는 우리랑 같이 앉을 수 없어"와 같은 상황입니다.

404 Not Found — 리소스가 존재하지 않음. GitHub은 무엇이 존재하는지에 대한 정보가 유출되는 것을 방지하기 위해, 접근 권한이 없는 프라이빗 저장소(private repositories)에 대해 403 대신 404를 반환하는 것으로 유명합니다. 이는 403과 404 사이의 방어 가능한 제3의 경로입니다.

409 Conflict — 상태 충돌 (State conflict). 중복된 키, 버전 불일치, "이미 사용 중인 이메일입니다"와 같은 상황입니다.

422 Unprocessable Content — JSON은 잘 파싱되었으나, 의미론적(semantically)으로 잘못됨. 기본적으로 유효성 검사(Validation) 실패를 의미합니다. 이는 WebDAV 전용 코드로 시작되었으나, "구문 오류 (syntactically broken)" (400)와 "논리적 오류 (logically wrong)"(이전에는 적절한 코드가 없었음) 사이의 간극 때문에 업계에서 재사용되었습니다.

429 Too Many Requests — 속도 제한 (Rate limited). 2012년 4월 RFC 6585에 의해 정의되었습니다. 클라이언트에게 얼마나 기다려야 하는지 알려주는 Retry-After 헤더를 포함해야 합니다.

500 Internal Server Error — 서버가 고장 남.

502 Bad Gateway — 업스트림(upstream)이 고장 남.

503 Service Unavailable — 서버가 다운되었거나 과부하 상태임. 언제 복구될지 안다면 Retry-After를 포함하세요.

401과 403의 혼동은 시니어 엔지니어들이 운영 시스템(production systems)에서 반복적으로 실수하는 것을 제가 목격해 온 부분입니다. 암기법을 알려드리자면: 401은 "당신은 누구입니까?", 403은 "당신이 누구인지 알지만, 안 됩니다"를 의미합니다.

URL 설계: 트레일링 슬래시(Trailing Slash)와 기타 종교적 논쟁

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0