1. 라이브러리
  2. HTTP와 웹
  3. API와 웹 서비스

업데이트됨 1개월 전

당신이 지금까지 방문한 모든 웹 페이지는 동일한 방식으로 작동했습니다. URL을 입력하면 브라우저가 요청을 보내고, 서버가 표현을 돌려보냈습니다. 링크는 다른 URL로 안내했고, 북마크는 위치를 기억했으며, 뒤로 가기 버튼이 작동했던 것은 각 페이지가 고유한 주소의 독립적인 개체였기 때문입니다.

REST는 API도 같은 방식으로 작동해야 한다는 인식입니다.

철학

REST(Representational State Transfer)는 프로토콜이 아닙니다. 구현해야 할 표준도 아닙니다. 웹을 성공으로 이끈 패턴이 API도 성공으로 이끌어야 한다고 말하는 아키텍처 철학입니다.

HTTP 설계에 직접 참여했던 Roy Fielding은 2000년 박사 논문에서 REST를 정의했습니다. 하지만 그는 새로운 것을 발명한 것이 아니라 이미 작동하고 있는 것을 설명한 것입니다. 웹이 수십억 개의 페이지로 확장될 수 있었던 것은 특정 아키텍처적 선택 덕분이었습니다. REST는 말합니다: API에도 동일한 선택을 하라고.

이름이 모든 것을 말해줍니다. 리소스를 요청하면 현재 상태표현이 당신에게 전달됩니다. 그게 전부입니다. 그것이 REST입니다.

리소스: 주소가 있는 것들

리소스는 이름을 붙일 수 있는 모든 것입니다. 사용자, 문서, 장바구니, 검색 결과. 이야기할 수 있다면 리소스가 될 수 있습니다.

모든 리소스는 주소—URI—를 가집니다:

/users/123           # 특정 사용자
/posts/456/comments  # 특정 게시물의 댓글
/products            # 모든 제품의 컬렉션

당연해 보입니다. 물론 모든 것에는 주소가 있죠. 하지만 개발자들이 API를 만들 때 얼마나 빨리 이것을 버리는지 보세요. /getUserProfile이나 /fetchOrderDetails 같은 엔드포인트를 만들어버립니다—주소인 척하는 동사들입니다. REST는 그러지 말라고 합니다. 사용자 프로필은 /users/123에 있습니다. 가져오는 것이 아니라 브라우저가 URL을 요청하듯 요청하는 것입니다.

리소스는 명사입니다. 항상 명사여야 합니다.

HTTP 메서드: 이미 있는 동사들

HTTP에는 이미 동사가 있습니다. 사용하세요.

GET은 리소스를 가져옵니다. 아무것도 변경하지 않습니다. 천 번 호출해도 같은 것을 돌려받을 뿐, 아무 일도 일어나지 않습니다. 이를 안전하고 멱등적이라고 합니다.

GET /users/123      # 사용자 123 조회
GET /posts          # 모든 게시물 조회

POST는 새로운 것을 만듭니다. 두 번 호출하면 두 개가 생길 수 있습니다. 안전하지도 멱등적이지도 않습니다—세상을 바꿉니다.

POST /users         # 새 사용자 생성
POST /comments      # 새 댓글 생성

PUT은 리소스 전체를 교체합니다. 완전한 새 버전을 보내세요. 같은 데이터로 다섯 번 호출해도 같은 결과가 나옵니다—멱등적입니다.

PUT /users/123      # 사용자 123을 이 데이터로 교체

PATCH는 리소스의 일부를 수정합니다. 변경하려는 필드만 보내면 됩니다.

PATCH /users/123    # 특정 필드만 수정

DELETE는 리소스를 제거합니다. 같은 것을 두 번 삭제해도? 어느 쪽이든 사라집니다—멱등적입니다.

DELETE /users/123   # 사용자 123 제거

웹은 이미 이 방법을 알고 있었습니다. REST는 그저 말했습니다: 의도적으로 하라고.

상태 코드: 응답의 첫 마디

본문을 읽기 전에 상태 코드가 무슨 일이 일어났는지 알려줍니다.

2xx—성공했습니다:

  • 200 OK: 요청하신 것 여기 있습니다
  • 201 Created: 원하시던 새 항목을 만들었습니다
  • 204 No Content: 완료했습니다, 보여드릴 것은 없습니다

4xx—클라이언트가 잘못했습니다:

  • 400 Bad Request: 보내신 것을 이해하지 못합니다
  • 401 Unauthorized: 누구시죠?
  • 403 Forbidden: 누구신지 압니다. 안 됩니다.
  • 404 Not Found: 이 주소에는 아무것도 없습니다
  • 409 Conflict: 그렇게 할 수 없습니다—현재 상태와 충돌합니다
  • 429 Too Many Requests: 속도를 줄여주세요

5xx—서버가 망가졌습니다:

  • 500 Internal Server Error: 저희 쪽에서 뭔가 잘못되었습니다
  • 502 Bad Gateway: 저희 뒤에 있는 무언가가 망가졌습니다
  • 503 Service Unavailable: 과부하 상태이거나 점검 중입니다

올바른 상태 코드를 사용하는 것은 사소한 규칙에 집착하는 게 아닙니다. 소통입니다. 401을 받은 클라이언트는 재인증해야 한다는 것을 압니다. 429를 받으면 속도를 줄여야 한다는 것을. 404를 받으면 재시도해도 소용없다는 것을.

작동하게 만드는 제약들

REST는 단순히 "HTTP를 올바르게 사용하라"가 아닙니다. 규모 확장성, 신뢰성, 진화를 가능하게 하는 아키텍처적 제약의 집합입니다.

무상태성: 모든 요청에는 서버가 처리하는 데 필요한 모든 것이 담겨 있습니다. 호출 사이에 당신이 누구인지 기억하는 서버 측 "세션"이 없습니다. 불편하게 들릴 수 있습니다—매번 자격증명을 보내야 하니까요. 하지만 바로 이것이 어떤 서버도 어떤 요청도 처리할 수 있게 해줍니다. 고정 세션 없음. 동기화 악몽 없음. 수평 확장이 무한히 가능합니다.

클라이언트-서버 분리: 클라이언트와 서버는 독립적으로 발전합니다. 인터페이스가 안정적으로 유지되는 한 어느 쪽이든 완전히 재작성할 수 있습니다. iOS 앱, Android 앱, 웹 앱 모두 동일한 API와 통신합니다. 서버는 어떤 종류의 클라이언트인지 신경 쓰지 않습니다.

캐시 가능성: 응답은 캐시될 수 있는지 여부를 알립니다. 가능한 경우 중간 계층(CDN, 브라우저 캐시, 프록시 서버)이 서버에 도달하지 않고 응답을 제공할 수 있습니다. 이것이 웹이 수십억 건의 요청을 처리하는 방법입니다—대부분은 원본 서버에 도달하지 않습니다.

계층화 시스템: 클라이언트는 최종 서버와 통신하는지 중간 계층과 통신하는지 알 수 없습니다. 이를 통해 클라이언트 코드를 변경하지 않고 로드 밸런서, 캐시, 보안 계층, API 게이트웨이를 추가할 수 있습니다.

일관된 인터페이스: 모든 사람이 동일한 방식으로 리소스와 상호작용합니다. 새로운 REST API를 사용하기 위해 특별한 지식이 필요하지 않습니다—동사를 알고, URI를 이해하며, 상태 코드를 읽을 수 있습니다. 패턴이 전달됩니다.

컬렉션과 개별 항목

URI는 컬렉션(리소스 그룹)과 개별 리소스를 구분합니다.

GET /users          # 사용자 컬렉션
GET /users/123      # 특정 사용자

POST /users         # 컬렉션에 추가 (사용자 생성)
DELETE /users/123   # 컬렉션에서 제거 (사용자 삭제)

중첩된 리소스는 관계를 나타냅니다:

GET /users/123/posts      # 사용자 123의 게시물
GET /posts/456/comments   # 게시물 456의 댓글

하지만 너무 깊이 중첩하지 마세요. /users/123/posts/456/comments/789/likes는 다루기 불편합니다. 리소스가 독립적으로 존재할 수 있다면 최상위 주소를 부여하세요.

쿼리 파라미터: 컬렉션 세부 조정하기

쿼리 파라미터는 수많은 특수 엔드포인트를 만들지 않고도 컬렉션을 필터링하고, 정렬하고, 페이지를 나눕니다.

GET /users?role=admin              # 역할로 필터링
GET /posts?sort=-created_at        # 날짜 내림차순 정렬
GET /users?page=2&per_page=20      # 페이지네이션
GET /users?fields=id,name,email    # 특정 필드 선택

컬렉션 리소스는 동일하게 유지됩니다. 쿼리 파라미터는 요청하는 내용을 세밀하게 조정합니다.

요청과 응답의 구조

일반적인 REST 교환:

요청:

POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123

{
  "name": "Jane Smith",
  "email": "jane@example.com"
}

응답:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /users/789

{
  "id": "789",
  "name": "Jane Smith",
  "email": "jane@example.com",
  "created_at": "2024-01-15T10:30:00Z"
}

주목하세요: 201 Created(200이 아님). Location 헤더는 새 리소스가 어디에 있는지 알려줍니다. 응답에는 서버가 지정한 ID와 함께 생성된 리소스가 포함됩니다. 모든 것이 소통합니다.

멱등성이 중요한 이유

분산 시스템에서 요청은 실패합니다. 네트워크는 패킷을 떨어뜨립니다. 클라이언트는 재시도합니다. 재시도가 성공했지만 원래 요청도 성공했다면 어떻게 될까요?

멱등적 연산에서는 문제가 없습니다:

  • 같은 리소스를 두 번 GET해도? 같은 것을 받습니다.
  • 같은 리소스를 두 번 DELETE해도? 삭제됩니다(또는 이미 삭제되었습니다).
  • 같은 데이터로 두 번 PUT해도? 같은 결과입니다.

POST의 경우 중복이 생길 수 있습니다. 그래서 신중한 API 설계에서는 멱등성 키를 사용합니다—클라이언트가 보내는 고유 식별자로 서버가 재시도를 인식할 수 있게 합니다.

멱등성 이해는 학문적인 것이 아닙니다. 신뢰할 수 있는 시스템을 구축하는 방법입니다.

잘못되는 것들

부작용이 있는 GET. 절대 안 됩니다. GET은 안전해야 합니다. 브라우저가 링크를 사전 로드하거나, 크롤러가 API를 인덱싱하거나, 캐시가 오래된 응답을 제공하더라도—GET은 아무것도 변경해서는 안 됩니다.

본문에 오류가 있는 200 OK. 상태 코드는 기계를 위한 것입니다. {"error": "User not found"}와 함께 200을 반환하면 상태 코드를 확인하는 클라이언트는 모든 것이 잘 됐다고 생각합니다.

URL에 동사 사용. /createUser, /deletePost, /updateOrder—그냥 불필요한 절차를 얹은 RPC를 다시 만든 겁니다. HTTP 메서드를 동사로 사용하세요.

일관성 없는 규칙. 여기서는 snake_case, 저기서는 camelCase. /user 단수, /products 복수. 규칙을 정하고 지키세요.

버전 관리 없는 변경. 엔드포인트가 반환하는 내용을 변경하면 모든 클라이언트가 깨집니다. 처음부터 API 버전을 관리하세요.

단순함의 함정

REST는 단순해 보입니다. 리소스, 동사, 상태 코드—누구든 오후 한 나절이면 이해할 수 있습니다.

함정은 이해가 곧 따름이라고 생각하는 것입니다. 개발자들은 리소스를 모델링하는 것보다 더 직접적으로 느껴진다는 이유로 /api/performAction 같은 엔드포인트를 만들며 RPC를 끊임없이 재발명합니다. 상태 코드는 확인하는 것이 추가 작업이라는 이유로 무시합니다. API가 "내부용"이라는 이유로 버전 관리를 건너뜁니다.

REST의 제약은 이해하기는 쉽고 따르기는 어렵습니다. 규율 자체가 요점입니다. 이 제약들이 조합 가능하고, 캐시 가능하며, 진화할 수 있는 시스템을 만들기 때문에 웹은 수십억 명으로 확장되었습니다. 당신의 API도 그 유산을 이어받을 수 있습니다—제약을 실제로 따른다면요.

REST API에 관해 자주 묻는 질문들

REST와 RESTful의 차이는 무엇인가요?

REST는 아키텍처 스타일입니다. "RESTful"은 REST 원칙을 따르는 API를 설명합니다. 실제로 완전한 RESTful API는 드뭅니다—대부분은 필요한 부분만 구현하고 HATEOAS 같은 제약은 무시합니다. "REST API"라는 용어는 REST 원칙을 엄격히 따르는지 여부와 관계없이 "JSON을 사용하는 HTTP API"의 약어가 되었습니다.

PUT과 PATCH는 언제 사용해야 하나요?

PUT은 리소스 전체를 교체합니다. 완전한 새 버전을 보내세요—포함하지 않은 필드는 삭제되거나 기본값으로 초기화될 수 있습니다. PATCH는 지정한 필드만 수정합니다. 클라이언트가 항상 완전한 리소스를 가지고 있을 때는 PUT을 사용하세요. 큰 리소스에서 몇 개의 필드만 수정할 때는 PATCH를 사용하세요.

REST에서 인증은 어떻게 처리하나요?

무상태성이란 모든 요청에 자격증명이 포함된다는 것을 의미합니다. 일반적으로 Bearer 토큰(JWT 또는 OAuth 액세스 토큰)이 담긴 Authorization 헤더를 사용합니다. 서버는 세션 상태를 저장하지 않고 각 요청마다 토큰을 검증합니다. 헤더나 쿼리 파라미터의 API 키도 또 다른 방법이지만 보안성이 낮습니다.

리소스에 복수형과 단수형 명사 중 어느 것을 사용해야 하나요?

복수형을 사용하세요. 컬렉션에는 /users, 개별 항목에는 /users/123. 단수형은 어색한 불일치를 만들어냅니다—/user가 컬렉션인지 특정 사용자인지 불분명합니다. 복수형이 더 명확하고 일반적입니다.

REST API 버전은 어떻게 관리하나요?

URL 버전 관리(/v1/users, /v2/users)가 가장 일반적이고 가장 눈에 잘 띕니다. 헤더 버전 관리(Accept: application/vnd.api.v2+json)는 URL을 깔끔하게 유지하지만 브라우저에서 테스트하기 어렵습니다. 쿼리 파라미터 버전 관리(/users?version=2)는 작동하지만 리소스 주소를 오염시킵니다. 하나를 선택하고 일관성을 유지하세요.

이 페이지가 도움이 되었나요?

😔
🤨
😃