이 문서의 과거 버전 (r1)을 보고 있습니다. 수정일: 2026.02.12 06:25
REST API는 월드 와이드 웹의 아키텍처 스타일을 따르는 API 설계 방식이다. 로이 필딩이 2000년 그의 박사 논문에서 제안한 REST 아키텍처 스타일을 웹 서비스에 적용한 것이다. 이는 HTTP 프로토콜의 장점을 최대한 활용하여, 자원을 중심으로 시스템을 구성하고 표준화된 방법으로 상호작용하도록 한다.
주요 목표는 단순성, 확장성, 성능, 신뢰성을 갖춘 분산 시스템을 구축하는 것이다. REST API는 특정 기술이나 플랫폼에 종속되지 않으며, HTTP 메서드, URI, 상태 코드 등의 웹 표준을 그대로 사용한다. 이를 통해 서버와 클라이언트가 느슨하게 결합되어 독립적으로 진화할 수 있다.
REST의 핵심 원칙은 자원을 URI로 식별하고, HTTP 메서드로 자원에 대한 행위를 표현하며, 표현을 통해 데이터를 교환하는 것이다. 또한, 각 요청은 필요한 모든 정보를 담아야 하는 무상태성을 지녀야 한다. 이러한 원칙들은 SOAP와 같은 복잡한 프로토콜 기반의 웹 서비스에 비해 가볍고 구현이 용이하다는 장점을 제공한다.
현대의 웹 및 모바일 애플리케이션, 마이크로서비스 아키텍처에서 데이터를 제공하고 통신하는 데 사실상의 표준으로 자리 잡았다. JSON 형식이 널리 사용되면서, 기계와 인간 모두 이해하기 쉬운 API를 설계하는 데 기여한다.
REST 아키텍처 스타일의 핵심은 자원(Resource)을 중심으로 시스템을 구성하는 것이다. 여기서 자원은 문서, 이미지, 사용자 정보, 주문 상태 등 서버가 제공하는 모든 정보나 데이터를 의미한다. 각 자원은 고유한 식별자인 URI를 가지며, 클라이언트는 이 URI를 통해 특정 자원을 요청하고 조작한다.
자원 자체는 추상적인 개념이며, 클라이언트와 서버 간에 실제로 교환되는 것은 자원의 표현(Representation)이다. 예를 들어, '사용자'라는 자원은 JSON 형식, XML 형식, 또는 HTML 형식 등 다양한 표현으로 전달될 수 있다. 클라이언트는 HTTP 헤더를 통해 원하는 표현 형식을 요청하고, 서버는 적절한 표현을 응답으로 반환한다.
REST의 또 다른 중요한 제약 조건은 무상태성(Statelessness)이다. 이는 각 클라이언트 요청이 그 자체로 완전한 정보를 포함해야 함을 의미한다. 서버는 클라이언트의 이전 요청 상태를 저장하지 않으며, 요청을 처리하는 데 필요한 모든 정보(예: 인증 토큰, 세션 데이터)는 해당 요청 내에 포함되어야 한다. 이는 서버의 확장성을 높이고, 요청 처리를 단순화하는 장점을 가진다.
개념 | 설명 | 예시 |
|---|---|---|
자원(Resource) | 서버가 관리하는 고유한 정보나 데이터. | 사용자, 주문, 상품 |
URI | 자원을 고유하게 식별하는 주소. |
|
표현(Representation) | 자원의 특정 상태를 전달하는 구체적인 형식. | JSON, XML, HTML |
무상태성(Statelessness) | 서버가 클라이언트의 세션 상태를 저장하지 않는 특성. | 요청마다 인증 토큰을 포함해야 함 |
REST API의 핵심은 모든 것을 자원(Resource)으로 추상화하고, 각 자원을 고유한 URI(Uniform Resource Identifier)로 식별하는 것이다. 자원은 서버가 관리하는 모든 정보나 데이터를 의미하며, 사용자, 주문, 상품, 문서 등 구체적인 객체부터 '오늘의 할일'과 같은 가상의 개념까지 포함될 수 있다. 각 자원은 하나 이상의 표현(Representation)을 가지며, 클라이언트는 URI를 통해 해당 자원의 표현을 요청하고 조작한다.
URI 설계는 자원의 계층적 관계를 반영하는 것이 일반적이다. 예를 들어, /users는 사용자 컬렉션을, /users/123은 ID가 123인 특정 사용자 자원을 식별한다. 자원 간의 관계는 URI 경로로 표현되며, /users/123/orders와 같이 특정 사용자의 주문 목록을 나타낼 수 있다. URI는 자원의 위치나 구조를 직관적으로 전달해야 하므로, 동사보다는 명사를 사용하고 계층 구조를 명확히 하는 것이 원칙이다.
URI는 자원의 이름이자 주소 역할을 하지만, 그 자체가 자원을 의미하지는 않는다. 동일한 자원이 여러 URI를 가질 수도 있으며, 서버는 요청된 URI에 따라 적절한 자원의 표현(예: JSON 또는 XML 형식)을 반환한다. 이때 URI는 변경 없이도 자원의 표현 형식이 변경될 수 있다는 점이 중요하다. 예를 들어, /users/123이라는 URI는 클라이언트의 Accept 헤더에 따라 JSON이나 XML 등 다른 표현으로 응답할 수 있다.
효율적인 URI 설계를 위한 몇 가지 관례가 존재한다. 컬렉션을 나타낼 때는 복수형 명사를 사용하는 것이 일반적이며(/articles), 특정 요소를 식별할 때는 단수형이나 ID를 결합한다(/articles/5). 또한, URI는 소문자를 사용하고, 가독성을 위해 하이픈(-)을 활용하며, 파일 확장자(예: .json)를 포함시키지 않는 것이 권장된다. 이러한 일관된 명명 규칙은 API의 예측 가능성과 사용성을 높이는 데 기여한다.
자원(Resource)은 실제 데이터나 서비스를 추상화한 개념이며, 표현(Representation)은 그 자원의 특정 상태나 정보를 전송 가능한 형태로 구체화한 것이다. 클라이언트는 서버가 관리하는 자원 자체에 직접 접근하지 않고, 자원의 표현을 주고받으며 상호작용한다. 예를 들어, '사용자'라는 자원은 JSON 형식의 데이터, XML 문서, HTML 페이지 등 다양한 표현을 가질 수 있다.
표현은 HTTP 헤더를 통해 그 형식이 명시된다. Content-Type 헤더는 서버가 응답으로 보내는 표현의 미디어 타입(예: application/json, application/xml)을 클라이언트에게 알린다. 반대로 클라이언트는 Accept 헤더를 사용해 자신이 이해할 수 있는 표현 형식을 서버에 요청할 수 있다. 이를 통해 동일한 URI에 대해 서로 다른 표현을 요청하고 제공하는 콘텐츠 협상이 가능해진다.
표현의 설계는 API의 사용성과 효율성에 큰 영향을 미친다. 일반적으로 JSON은 경량화되고 가독성이 좋아 현대 웹 API에서 가장 널리 사용되는 표현 형식이다. 필요한 데이터만 포함하고, 중첩 구조를 적절히 사용하며, 일관된 명명 규칙을 적용하는 것이 중요하다. 아래는 사용자 정보를 표현하는 JSON의 간단한 예시이다.
필드명 | 데이터 타입 | 설명 |
|---|---|---|
| 정수(Integer) | 사용자의 고유 식별자 |
| 문자열(String) | 사용자 계정명 |
| 문자열(String) | 이메일 주소 |
| 문자열(String) | 계정 생성 일시 (ISO 8601 형식) |
표현은 자원의 전체 상태를 전달할 수도 있고, 부분적인 변경 사항만을 전달할 수도 있다. 예를 들어, PUT 메서드는 자원의 전체 표현을 교체하는 데 사용되며, PATCH 메서드는 자원의 부분적인 수정을 지시하는 부분 표현에 사용된다.
클라이언트가 서버로 보내는 각 요청은 그 자체로 모든 필요한 정보를 포함해야 한다. 즉, 서버는 이전 요청의 정보를 저장하거나 현재 요청의 상태를 결정하기 위해 세션 상태를 사용해서는 안 된다. 서버는 요청을 처리하는 데 필요한 모든 데이터를 요청 자체에서 얻을 수 있어야 한다.
이 원칙은 서버의 확장성을 크게 향상시킨다. 서버가 클라이언트 상태를 저장할 필요가 없기 때문에, 요청은 시스템 내의 어떤 서버 인스턴스로도 자유롭게 라우팅될 수 있다. 이는 로드 밸런싱을 단순화하고, 서버 장애 시에도 다른 인스턴스가 중단 없이 요청을 이어받을 수 있게 한다. 또한, 서버 측의 상태 저장소 관리가 불필요해져 구현이 단순해진다.
무상태성은 캐싱의 효율성도 높인다. 응답이 특정 클라이언트 상태에 묶이지 않기 때문에, 동일한 요청에 대한 응답을 다른 클라이언트에게도 재사용할 가능성이 커진다. 그러나 모든 상태를 매 요청마다 전송해야 하므로, 요청의 오버헤드가 증가할 수 있다는 단점도 있다. 일반적으로 인증 토큰 같은 정보는 매 요청의 HTTP 헤더에 포함시켜 전달한다.
특성 | 설명 |
|---|---|
장점 | 확장성 향상, 단순성, 캐싱 효율 증가 |
단점 | 요청 오버헤드 증가, 매 요청마다 인증 정보 필요 |
위반 예시 | 서버 측 세션을 사용하여 로그인 상태 관리 |
준수 예시 | JWT나 OAuth Bearer 토큰을 Authorization 헤더에 담아 전송 |
HTTP 메서드는 URI로 식별된 자원에 대해 수행할 작업의 종류를 정의한다. REST API 설계에서 각 메서드는 고유한 의미와 기대되는 부수 효과를 가지며, 이를 올바르게 적용하는 것이 중요하다.
주요 메서드의 의미는 다음과 같다.
* GET: 자원의 상태나 표현을 조회한다. 서버의 상태를 변경하지 않아야 한다.
* POST: 새로운 자원을 생성하거나, 기존 자원에 대해 처리(예: 주문 제출, 검색 실행)를 요청한다. 일반적으로 새로운 URI가 생성된다.
* PUT: 대상 URI에 자원을 완전히 대체하거나 생성한다. 클라이언트가 변경할 전체 표현을 알고 있어야 한다.
* PATCH: 자원의 부분적인 수정을 요청한다. 변경할 필드만 지정하는 점이 PUT과 다르다.
이 메서드들은 멱등성과 안전성이라는 두 가지 중요한 속성으로 분류된다. 안전한 메서드는 서버 상태를 변경하지 않아야 하며, GET이 대표적이다. 멱등한 메서드는 동일한 요청을 한 번 보내는 것과 여러 번 보내는 것이 서버에 동일한 최종 상태를 만든다. GET, PUT, DELETE는 멱등하지만, POST와 PATCH는 일반적으로 멱등하지 않다[1]. 이러한 속성을 이해하면 클라이언트가 요청 실패 시 재시도할 수 있는 메서드를 판단하고, 캐싱과 같은 최적화를 적용하는 데 도움이 된다.
메서드 | 의미 | 멱등성 | 안전성 |
|---|---|---|---|
GET | 자원 조회 | 예 | 예 |
POST | 자원 생성/처리 | 아니오 | 아니오 |
PUT | 자원 전체 대체/생성 | 예 | 아니오 |
DELETE | 자원 삭제 | 예 | 아니오 |
PATCH | 자원 부분 수정 | 일반적으로 아니오 | 아니오 |
HTTP 메서드는 클라이언트가 서버에 요청할 의도나 목적을 명시한다. 각 메서드는 고유한 의미와 특성을 가지며, REST API 설계에서는 이 의미론적 차이를 준수하는 것이 중요하다.
주요 메서드의 의미는 다음과 같다.
메서드 | 의미 | 일반적인 용도 |
|---|---|---|
자원의 표현을 조회한다. | 서버의 데이터를 변경하지 않고 정보를 검색할 때 사용한다. | |
새로운 자원을 생성하거나, 처리할 데이터를 제출한다. | 새로운 주문 생성, 게시글 작성, 폼 데이터 처리 등에 사용한다. | |
대상 URI에 자원을 전체적으로 대체(생성 또는 업데이트)한다. | 특정 ID를 가진 자원의 전체 내용을 업데이트할 때 사용한다. | |
지정한 자원을 삭제한다. | 특정 데이터를 서버에서 제거할 때 사용한다. | |
자원의 부분적인 수정을 수행한다. | 자원의 일부 필드만을 업데이트할 때 사용한다[2]. |
GET과 HEAD 메서드는 안전(Safe)한 메서드로 간주되며, 서버의 상태를 변경시키지 않아야 한다. PUT, DELETE, PATCH 메서드는 멱등(Idempotent)한 성질을 가진다. 즉, 동일한 요청을 여러 번 보내도 한 번 보낸 것과 같은 효과를 가져야 한다. 예를 들어, 같은 PUT 요청을 두 번 보내면 첫 번째 요청은 자원을 생성 또는 업데이트하고, 두 번째 요청은 아무런 변화 없이 동일한 상태를 유지해야 한다. 반면, POST는 일반적으로 멱등하지 않으며, 같은 요청을 반복하면 새로운 자원이 반복적으로 생성될 수 있다.
멱등성은 특정 연산을 한 번 수행하든 여러 번 수행하든 그 결과가 동일하게 유지되는 속성을 의미한다. HTTP 메서드에서 멱등성은 클라이언트가 동일한 요청을 반복해도 서버의 상태가 한 번 요청한 것과 동일하게 유지되는지를 판단하는 기준이다. 예를 들어, PUT 메서드는 동일한 요청을 여러 번 보내더라도 최종 자원 상태는 첫 번째 요청과 동일하다. DELETE 메서드도 자원을 삭제한 후에는 해당 자원이 더 이상 존재하지 않으므로, 동일한 삭제 요청을 반복해도 결과적으로 자원이 없는 상태로 유지된다. 이와 달리 POST 메서드는 일반적으로 멱등하지 않다. 동일한 데이터로 POST 요청을 반복하면, 매번 새로운 자원이 생성되어 서버 상태가 달라지기 때문이다.
HTTP 메서드 | 멱등성 여부 | 주요 이유 |
|---|---|---|
GET | 예 | 자원을 조회만 하므로 서버 상태를 변경하지 않음 |
PUT | 예 | 동일한 표현으로 자원을 완전히 대체함 |
DELETE | 예 | 자원 삭제 후에도 '자원 없음' 상태로 결과 동일 |
POST | 아니요 | 요청마다 새로운 자원을 생성하거나 부수 효과가 발생함 |
PATCH | 일반적으로 아니요 | 수행하는 연산에 따라 달라질 수 있음[3] |
안전성은 특정 HTTP 메서드가 서버의 자원 상태를 변경하지 않는지를 나타내는 속성이다. 안전한 메서드는 서버에 부수 효과를 일으키지 않아야 하며, 주로 데이터를 읽는 용도로 사용된다. 대표적인 안전한 메서드는 GET, HEAD, OPTIONS가 있다. 이 메서드들은 자원의 표현을 가져오거나 정보를 조회할 뿐, 서버 측 데이터를 생성, 수정, 삭제하지 않는다. 따라서 안전한 메서드는 캐싱, 프리페칭, 웹 크롤러 등에서 상대적으로 자유롭게 사용될 수 있다. 반면, POST, PUT, DELETE, PATCH는 서버 상태를 변경하므로 안전하지 않은 메서드로 분류된다.
멱등성과 안전성은 REST API 설계와 클라이언트의 요청 재시도 로직에 중요한 영향을 미친다. 네트워크 장애 등으로 인해 응답을 받지 못한 경우, 클라이언트는 멱등한 요청은 안전하게 재전송할 수 있다. 그러나 안전하지 않은 요청, 특히 멱등하지 않은 POST 요청의 재전송은 의도치 않은 중복 자원 생성 등의 문제를 초래할 수 있으므로 주의가 필요하다.
URI는 자원을 고유하게 식별하는 주소이다. 효율적이고 직관적인 URI 설계는 API의 사용성과 유지보수성을 크게 향상시킨다.
URI 설계의 핵심 원칙은 자원을 명사로 표현하고 계층 구조를 반영하는 것이다. 동사보다는 명사를 사용하여 자원의 상태나 행위가 아닌 자원 자체를 가리키도록 한다. 예를 들어, GET /users/123은 "사용자 123번을 조회한다"는 행위보다 "사용자 123번이라는 자원"을 식별한다. 자원 간의 관계는 슬래시(/)를 사용한 계층 구조로 표현한다. GET /users/123/orders는 사용자 123번과 그 사용자의 주문 목록 간의 포함 관계를 명확히 보여준다.
자원의 컬렉션과 개별 요소는 URI에서 구분하여 명시하는 것이 일반적이다. 컬렉션은 복수형 명사를 사용하고, 개별 요소는 컬렉션 URI 뒤에 식별자를 추가하는 패턴을 따른다.
자원 | 컬렉션 (목록/생성) | 요소 (조회/수정/삭제) |
|---|---|---|
사용자 |
|
|
주문 |
|
|
URI는 소문자를 사용하고, 가독성을 위해 하이픈(-)을 단어 구분자로 채택하는 것이 권장된다. 밑줄(_)이나 대문자는 사용을 피한다. 또한, URI는 자원의 위치를 나타내야 하며, 구현 세부 사항(예: 파일 확장자 .php, 동작을 나타내는 getUsers)이나 불필요한 정보를 포함해서는 안 된다. 쿼리 파라미터는 자원을 식별하는 주요 수단이 아니라, 정렬, 필터링, 검색, 페이징과 같은 부가적인 작업에 사용한다. 예를 들어, GET /users?active=true&sort=name은 활성 사용자만 이름순으로 정렬하여 조회하는 요청이다.
URI는 자원을 식별하는 주소이므로, 동사보다는 명사를 사용하여 자원의 본질을 나타내는 것이 기본 원칙이다. 예를 들어, 사용자 목록을 조회하는 행위를 표현하기 위해 /getUsers보다는 /users를 사용한다. 이는 HTTP 메서드가 해당 자원에 대해 수행할 작업(조회, 생성, 수정, 삭제)을 정의하도록 분리하는 데 도움이 된다.
URI는 계층 구조를 통해 자원 간의 관계를 직관적으로 표현한다. 예를 들어, 특정 사용자가 작성한 모든 게시글을 식별하려면 /users/{userId}/posts와 같은 패턴을 사용한다. 여기서 {userId}는 경로 변수로, 특정 사용자 자원을 구체적으로 지정한다. 이러한 계층 구조는 데이터 모델의 관계를 반영하며, API의 예측 가능성을 높인다.
일반적인 설계 관례는 다음과 같다.
* 컬렉션(자원의 집합)을 나타낼 때는 복수형 명사를 사용한다. (예: /users, /articles)
* 특정 자원(컬렉션 내의 단일 요소)을 식별할 때는 복수형 컬렉션 이름 뒤에 고유 식별자를 추가한다. (예: /users/{id}, /articles/{articleId}/comments/{commentId})
* 계층은 2단계를 초과하지 않도록 간결하게 유지하는 것이 좋다. 지나치게 깊은 중첩(예: /a/{aId}/b/{bId}/c/{cId}/d)은 오히려 URI를 복잡하게 만들고, 대안으로 평평한 구조를 고려할 수 있다[4].
URI 설계에서 자원의 집합(컬렉션)과 그 집합 내의 개별 요소를 구분하여 명명하는 것은 REST API의 일관성과 직관성을 높이는 중요한 관행이다. 일반적으로 컬렉션을 나타내는 경로는 복수형 명사를 사용하고, 특정 요소를 식별하는 경로는 컬렉션 경로 뒤에 요소의 식별자(ID)를 추가하는 패턴을 따른다.
예를 들어, 사용자들의 집합은 /users라는 복수형 URI로 표현한다. 이 엔드포인트에 GET 요청을 보내면 사용자 목록을 조회할 수 있고, POST 요청을 보내면 새로운 사용자를 생성할 수 있다. 반면, ID가 123인 특정 사용자는 /users/123이라는 URI로 접근한다. 이는 /user/123과 같은 단수형보다 계층 관계를 명확히 보여준다. 이 패턴은 다른 자원에도 동일하게 적용된다 (예: /articles, /articles/456).
이 원칙을 따르면 API의 구조가 예측 가능해지고, 클라이언트 개발자가 새로운 엔드포인트를 쉽게 이해할 수 있다. 단, 영어의 불규칙 복수형이나 의미상 단수형 컬렉션이 더 적합한 경우(예: /search는 동작을 나타내는 컨트롤러 URI로 간주될 수 있음)에는 예외가 발생할 수 있다. 중요한 것은 프로젝트 내에서 일관된 규칙을 세우고 이를 준수하는 것이다.
REST API에서 클라이언트와 서버는 자원의 실제 데이터가 아닌, 그 자원의 표현을 주고받는다. 표현은 특정 시점의 자원 상태를 전달하기 위한 형식으로, 주로 JSON이나 XML과 같은 구조화된 데이터 형식을 사용한다. 서버는 클라이언트의 요청에 따라 동일한 자원을 서로 다른 표현(예: JSON 또는 XML)으로 제공할 수 있다.
이때 미디어 타입은 교환되는 표현의 형식을 명시적으로 정의하는 역할을 한다. 주요 미디어 타입으로는 application/json과 application/xml이 널리 사용된다. 클라이언트는 Accept 헤더를 통해 선호하는 표현 형식을 서버에 요청할 수 있으며, 서버는 Content-Type 헤더를 응답에 포함하여 실제로 제공하는 표현의 형식을 알린다.
헤더 | 역할 | 예시 값 |
|---|---|---|
| 클라이언트가 이해할 수 있는 표현 형식을 서버에 요청 |
|
| 서버가 응답 본문에 사용한 실제 표현 형식을 알림 |
|
표현 형식의 선택은 API의 사용성과 상호운용성에 직접적인 영향을 미친다. 현대 웹 API에서는 가독성이 좋고 파싱이 쉬운 JSON이 사실상의 표준으로 자리 잡았다. 그러나 특정 도메인(예: SOAP 기반 시스템 통합)에서는 여전히 XML이 선호되기도 한다. API 설계자는 명확한 미디어 타입 협상을 통해 클라이언트와 서버 간의 효율적인 데이터 교환을 보장해야 한다.
JSON과 XML은 REST API에서 가장 널리 사용되는 두 가지 데이터 표현 형식이다. 둘 다 구조화된 데이터를 텍스트 형식으로 직렬화하여 네트워크를 통해 전송하는 데 적합하다.
JSON은 JavaScript Object Notation의 약자로, 경량의 데이터 교환 형식이다. 문법이 간결하고 가독성이 좋으며, 대부분의 현대 프로그래밍 언어에서 기본적으로 지원한다. 데이터는 키-값 쌍의 집합으로 구성되며, 배열과 중첩된 객체를 사용해 복잡한 계층 구조를 표현할 수 있다. 이러한 특성으로 인해 웹 및 모바일 애플리케이션 개발에서 사실상의 표준 형식이 되었다. 반면, XML은 Extensible Markup Language의 약자로, 태그를 사용해 데이터와 메타데이터를 기술한다. 스키마(XML Schema, DTD)를 통해 데이터 구조와 유효성을 엄격하게 정의할 수 있어, 금융이나 공공 부문 등 고도로 구조화된 문서 교환이 필요한 분야에서 여전히 선호된다.
두 형식의 주요 차이점은 다음과 같이 정리할 수 있다.
특성 | JSON | XML |
|---|---|---|
구문 | 간결한 키-값 쌍, 자바스크립트 객체와 유사 | 태그 기반의 마크업 언어 |
가독성 | 개발자에게 직관적이고 가볍다 | 사람과 기계 모두가 읽기 쉬우나 상대적으로 장황하다 |
구조 검증 | JSON Schema로 가능하지만 선택적이다 | XML Schema(XSD) 등 강력한 스키마 언어를 통한 엄격한 검증이 가능하다 |
네임스페이스 | 지원하지 않음 | 완전한 네임스페이스 지원으로 복합 문서 처리에 유리 |
데이터 타입 | 제한된 기본 타입(문자열, 숫자, 불리언, null, 배열, 객체) 지원 | 스키마를 통해 사용자 정의 데이터 타입 정의 가능 |
처리 성능 | 일반적으로 파싱 속도가 빠르고 오버헤드가 적다 | 문서 구조가 복잡할수록 파싱 오버헤드가 커질 수 있다 |
API 설계 시 형식 선택은 요구사항에 따라 결정된다. 클라이언트가 주로 웹 브라우저나 모바일 앱이라면 JSON이 효율적이다. 반면, 업계 표준이 XML을 요구하거나 복잡한 문서 구조와 메타데이터, 스키마 검증이 필수적인 경우에는 XML이 더 적합할 수 있다. 많은 현대 API는 Content-Type 및 Accept 헤더를 활용해 클라이언트가 선호하는 형식(JSON 또는 XML)으로 응답을 받을 수 있도록 양쪽을 모두 지원한다.
HTTP에서 Content-Type 헤더와 Accept 헤더는 클라이언트와 서버 간에 교환되는 데이터의 표현 형식을 명시하고 협상하는 데 사용되는 핵심 메커니즘이다. 이 두 헤더는 REST API 설계에서 요청과 응답의 미디어 타입을 명확히 정의함으로써 상호 운용성을 보장한다.
Content-Type 헤더는 엔터티 바디에 포함된 데이터의 실제 형식을 나타낸다. 주로 POST나 PUT 요청을 보낼 때, 클라이언트가 서버로 전송하는 데이터가 어떤 형식인지 알려주는 역할을 한다. 예를 들어, Content-Type: application/json은 요청 바디가 JSON 형식임을 의미한다. 서버는 이 헤더를 해석하여 데이터를 올바르게 파싱한다. 응답에서도 서버는 이 헤더를 사용하여 클라이언트에게 반환하는 데이터의 형식을 명시한다.
반면, Accept 헤더는 HTTP 요청에서 클라이언트가 서버로부터 받고자 하는 데이터의 표현 형식을 선호도 순서대로 나열한 것이다. 이를 통해 콘텐츠 협상이 이루어진다. 클라이언트가 Accept: application/json, application/xml;q=0.9와 같이 헤더를 보내면, 서버는 우선순위가 높은 application/json 형식으로 응답을 제공하려 시도한다. q 매개변수는 상대적 선호도를 나타내는 품질 값이다. 서버는 클라이언트가 요청한 형식을 지원하지 않을 경우 406 Not Acceptable 상태 코드로 응답할 수 있다.
헤더 | 방향 | 역할 | 주요 값 예시 |
|---|---|---|---|
Content-Type | 요청/응답 | 현재 메시지 바디의 실제 데이터 형식 명시 |
|
Accept | 요청 | 클라이언트가 선호하는 응답 데이터 형식 제시 |
|
이 두 헤더를 적절히 활용하는 것은 API의 명확성과 유연성을 높인다. 서버는 Content-Type을 검증하여 지원하지 않는 형식의 요청을 415 Unsupported Media Type 오류로 거절할 수 있다. 또한, 동일한 자원에 대해 JSON, XML 등 다양한 표현을 지원하는 API를 설계할 때 Accept 헤더 기반의 협상은 단일 URI를 유지하면서 클라이언트의 필요에 맞는 형식을 제공하는 효율적인 방법이다.
상태 코드는 HTTP 요청의 결과를 나타내는 세 자리 숫자이다. 서버는 클라이언트에게 요청이 어떻게 처리되었는지를 상태 코드를 통해 알린다. 이 코드는 크게 다섯 가지 클래스로 구분되며, 각 클래스의 첫 번째 숫자로 식별된다[5].
주요 상태 코드 클래스와 그 의미는 다음과 같다.
클래스 | 설명 | 대표 코드 예시 |
|---|---|---|
1xx (정보) | 요청을 받았으며, 프로세스를 계속 진행함 | 100, 101 |
2xx (성공) | 요청이 성공적으로 수신, 이해, 수용됨 | |
3xx (리다이렉션) | 요청 완료를 위해 추가 조치가 필요함 | 301, 304 |
4xx (클라이언트 오류) | 클라이언트의 요청에 문법 오류가 있거나 처리할 수 없음 | |
5xx (서버 오류) | 서버가 유효한 요청을 수행하지 못함 |
REST API 설계에서는 각 HTTP 메서드의 성공 및 실패 시나리오에 맞는 적절한 상태 코드를 반환하는 것이 중요하다. 예를 들어, 리소스 생성 요청(POST)이 성공하면 201 Created를, 본문 없이 성공만 알리려면 204 No Content를 사용한다. 클라이언트의 잘못된 요청에는 400 Bad Request를, 존재하지 않는 리소스에 접근할 때는 404 Not Found를 반환하여 문제의 원인을 명확히 전달해야 한다.
서버 오류를 나타내는 5xx 코드는 신중하게 사용해야 한다. 500 Internal Server Error는 서버 측의 예기치 않은 오류에 대한 일반적인 응답이다. 상태 코드를 일관되게 활용하면 클라이언트가 자동으로 오류를 처리하거나 사용자에게 명확한 안내를 제공하는 데 도움이 된다. 또한, 304 Not Modified와 같은 코드는 캐싱 효율성을 높이는 데 기여한다.
HTTP 상태 코드는 요청의 결과를 나타내는 3자리 숫자이다. 이 코드는 크게 다섯 가지 클래스로 구분되며, 각 클래스의 첫 번째 숫자로 식별된다. REST API 설계에서는 특히 2xx(성공), 4xx(클라이언트 오류), 5xx(서버 오류) 클래스의 코드를 명확하고 일관되게 사용하는 것이 중요하다.
2xx 클래스는 클라이언트의 요청이 성공적으로 수신되고 이해되어 처리되었음을 나타낸다. 가장 일반적인 코드는 200 OK로, 요청이 성공했음을 의미한다. 201 Created는 새로운 자원이 성공적으로 생성되었을 때 사용되며, 응답의 Location 헤더에 새 자원의 URI를 포함하는 것이 일반적이다. 204 No Content는 요청은 성공했으나 응답 본문에 반환할 내용이 없을 때 사용된다[6].
4xx 클래스는 클라이언트 측의 오류를 나타낸다. 즉, 요청 자체에 문법 오류가 있거나, 유효하지 않아 서버가 처리할 수 없음을 의미한다. 400 Bad Request는 요청의 구문이 잘못되었거나 유효성 검사를 통과하지 못했을 때 사용된다. 401 Unauthorized는 인증이 필요하지만 제공되지 않았거나 실패했음을, 403 Forbidden은 인증은 되었으나 요청한 자원에 대한 접근 권한이 없음을 나타낸다. 가장 유명한 코드 중 하나인 404 Not Found는 요청한 자원을 서버에서 찾을 수 없을 때 반환된다.
5xx 클래스는 서버 측에서 요청 처리에 실패했음을 나타낸다. 클라이언트의 요청은 유효했지만, 서버의 내부 문제로 인해 수행할 수 없었다는 의미이다. 500 Internal Server Error는 서버가 요청을 처리하는 과정에서 예상치 못한 오류를 만났을 때의 일반적인 응답이다. 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout 등은 주로 프록시나 로드 밸런서를 거치는 구성에서 서버나 업스트림 서비스의 문제를 나타낼 때 사용된다.
HTTP 상태 코드는 요청의 결과를 나타내는 세 자리 숫자이다. REST API 설계에서는 적절한 상태 코드를 사용하여 클라이언트가 요청 결과를 명확히 이해할 수 있도록 해야 한다. 주요 코드는 성공(2xx), 클라이언트 오류(4xx), 서버 오류(5xx)로 구분된다.
성공을 나타내는 2xx 코드 중 가장 일반적인 것은 200 OK이다. 이는 요청이 성공적으로 처리되었음을 의미하며, 주로 GET 요청의 응답에 사용된다. 201 Created는 새로운 자원이 성공적으로 생성되었을 때 사용되며, 응답 헤더의 Location 필드에 새 자원의 URI를 포함하는 것이 일반적이다. 204 No Content는 요청은 성공했으나 응답 본문에 반환할 내용이 없을 때 사용되며, DELETE 요청이나 일부 PUT, PATCH 요청 후에 자주 활용된다.
클라이언트 측 오류를 나타내는 4xx 코드는 요청 자체에 문제가 있음을 의미한다. 400 Bad Request는 요청의 구문이 잘못되었거나 필수 매개변수가 누락되는 등 일반적인 클라이언트 오류에 사용된다. 404 Not Found는 요청한 자원을 서버가 찾을 수 없을 때 반환된다. 이는 잘못된 URI를 사용했거나, 이미 삭제된 자원을 요청한 경우 발생할 수 있다.
서버 측 오류를 나타내는 5xx 코드 중 500 Internal Server Error는 가장 일반적인 서버 오류 코드이다. 이는 서버가 요청을 처리하는 중에 예상치 못한 상황을 맞아 처리에 실패했음을 나타낸다. 이 코드는 구체적인 오류 원인을 클라이언트에 노출시키지 않으면서 서버 내부 문제를 알리는 데 사용된다.
HATEOAS는 REST 아키텍처 스타일의 제약 조건 중 하나로, "Hypermedia As The Engine Of Application State"의 약자이다. 이는 클라이언트가 서버로부터 받은 응답에 포함된 하이퍼미디어 링크(주로 URI)를 통해 애플리케이션의 상태와 가능한 다음 동작을 발견하고 전이할 수 있어야 한다는 원칙이다. 즉, 서버는 단순히 데이터만 제공하는 것이 아니라, 해당 데이터와 관련된 가능한 후속 작업에 대한 링크 정보를 함께 제공해야 한다. 이를 통해 클라이언트는 서버의 API 엔드포인트 구조를 사전에 모두 알 필요 없이, 초기 진입점(예: API 루트)에서부터 제공되는 링크를 따라가며 전체 애플리케이션을 탐색할 수 있다.
이 원칙을 적용한 API 응답은 다음과 같은 형태를 가진다. 예를 들어, 주문 정보를 조회하는 GET 요청에 대한 응답이 단순히 주문 데이터만 반환하는 것이 아니라, 해당 주문을 수정하거나 취소할 수 있는 링크, 관련 상품 정보로 이동할 수 있는 링크 등을 포함한다.
```json
{
"orderId": 12345,
"status": "PROCESSING",
"totalAmount": 55000,
"_links": {
"self": { "href": "/orders/12345" },
"cancel": { "href": "/orders/12345", "method": "DELETE" },
"payment": { "href": "/orders/12345/payment" }
}
}
```
HATEOAS의 구현은 RESTful API의 성숙도 모델(Richardson Maturity Model)에서 최고 수준인 Level 3에 해당한다[7]. 이 접근 방식의 주요 장점은 서버가 URI 구조를 변경하거나 새로운 기능을 추가했을 때, 링크 정보만 업데이트하면 클라이언트는 하드코딩된 URI에 의존하지 않고도 자동으로 새로운 경로를 발견하여 적응할 수 있다는 점이다. 이로 인해 클라이언트와 서버의 결합도가 낮아지고, API의 진화와 유지보수가 용이해진다.
하지만 HATEOAS는 실제 산업 현장에서 널리 채용되기보다는 논의의 대상이 되는 경우가 많다. 그 이유는 구현의 복잡성 증가, 응답 페이로드의 크기 확대, 그리고 클라이언트 측에서 링크 관계를 파싱하고 처리해야 하는 부담 때문이다. 많은 실용적인 REST API 설계에서는 CRUD 인터페이스에 충실한 Level 2 수준까지 적용하고, HATEOAS는 선택적으로 특정 도메인(예: 복잡한 워크플로우를 가진 시스템)에 한정하여 활용한다.
REST API의 버전 관리 전략은 클라이언트와 서버 간의 호환성을 유지하면서 API를 발전시키기 위한 핵심적인 고려사항이다. 주로 URI에 버전을 포함시키는 방법과 HTTP 헤더를 이용하는 방법으로 나뉜다.
전략 | 방법 | 장점 | 단점 |
|---|---|---|---|
URI 버전 관리 | URI 경로( | 직관적이고 캐싱이 용이하며, 브라우저에서 직접 접근 가능 | URI가 버전 정보로 오염되어 REST의 자원 지향적 설계 원칙에 어긋난다는 비판이 있음 |
헤더 버전 관리 |
| URI가 깔끔하게 유지되며, 버전 협상이 가능 | 클라이언트 구현이 복잡해지고, 캐싱 설정이 어려울 수 있음 |
URI 버전 관리 방식은 구현과 디버깅이 간편하여 널리 사용된다. 특히 경로 기반 버전 관리(/v1/, /v2/)는 서로 다른 버전의 API를 완전히 별도의 엔드포인트로 관리할 수 있어 롤백과 배포가 용이하다. 반면, 헤더 버전 관리 방식은 HATEOAS 원칙을 더 잘 준수하며, 자원의 식별자(URI)가 변경되지 않는다는 장점을 가진다. 최근에는 API 설계 초기에 확장 가능성을 고려하여 버전 변경을 최소화하는 방향으로 나아가고 있다.
URI에 버전 정보를 포함시키는 방식은 REST API 버전 관리에서 가장 직관적이고 널리 사용되는 방법이다. 이 방식은 클라이언트가 요청하는 API의 버전을 URI 경로 자체에서 명확하게 식별할 수 있게 한다.
일반적인 패턴은 URI의 경로 시작 부분이나 끝 부분에 v1, v2와 같은 버전 번호를 포함하는 것이다. 예를 들어, /api/v1/users 또는 /api/users/v1과 같은 형태를 가진다. 전자의 방식이 더 일반적으로 사용된다. 이 방법의 가장 큰 장점은 명시적이고 캐시 가능하며, 브라우저나 curl과 같은 간단한 도구로도 특정 버전의 API에 쉽게 접근할 수 있다는 점이다. 또한, 서버 측에서도 버전별로 라우팅 로직을 분리하기가 상대적으로 용이하다.
그러나 이 방식은 REST의 원칙 중 하나인 자원의 고유한 식별자인 URI가 버전에 따라 변경된다는 비판을 받는다. 동일한 사용자 정보라는 자원이 /api/v1/users/123과 /api/v2/users/123이라는 서로 다른 URI를 가지게 되어, 자원의 정체성에 혼란을 줄 수 있다. 또한, 새로운 버전이 출시되어도 이전 버전의 URI를 유지해야 하므로 관리 포인트가 증가할 수 있다.
다음은 URI 버전 관리와 헤더 버전 관리의 주요 특징을 비교한 표이다.
특징 | URI 버전 관리 | 헤더 버전 관리 |
|---|---|---|
가시성 | URI에 명시적으로 표시되어 매우 높음 | 헤더에 숨겨져 있어 낮음 |
접근 용이성 | 브라우저, 책갈피, 로그 분석에 용이함 | 특별한 도구나 헤더 설정이 필요함 |
캐싱 | 버전별로 URI가 다르므로 별도 캐싱 가능 | 동일 URI에 대해 다른 버전 응답이 가능해 캐싱 주의 필요 |
REST 원칙 준수 | URI가 변경되므로 논란의 여지가 있음 | 동일 URI를 유지하므로 더 적합하다고 봄 |
클라이언트 변경 | 새 버전 사용을 위해 코드 내 URI 수정 필요 | 헤더 값만 변경하면 됨 |
서버 라우팅 | 경로 기반으로 버전 분기가 비교적 쉬움 | 헤더 파싱을 통한 분기가 필요함 |
결론적으로, URI 버전 관리는 단순함과 명확성으로 인해 많은 실무 환경에서 선호되지만, HATEOAS와 같은 RESTful 원칙을 엄격히 고수해야 하는 경우나 장기적으로 URI의 안정성을 보장하고 싶은 경우에는 다른 대안을 고려하게 된다.
버전 정보를 HTTP 요청 헤더에 포함시키는 방식은 URI를 변경하지 않고 API 버전을 관리하는 방법이다. 주로 Accept 헤더나 사용자 정의 헤더를 활용한다.
Accept 헤더에 미디어 타입을 확장하여 버전을 명시하는 것이 일반적인 접근법이다. 예를 들어, Accept: application/vnd.company.v2+json과 같이 요청하면, 서버는 해당 버전의 표현을 반환한다. 이 방식은 HATEOAS 원칙을 준수하며, 리소스의 식별자(URI)가 버전에 따라 변하지 않는다는 장점이 있다. 클라이언트가 원하는 버전을 명시적으로 요청하므로, 서버는 동일한 URI에 대해 여러 버전의 표현을 유연하게 제공할 수 있다.
다른 방법으로는 X-API-Version과 같은 사용자 정의 헤더를 이용하는 것이다. 그러나 사용자 정의 헤더는 표준화되지 않았으며, 일부 중간 프록시 서버나 캐시 시스템에서 무시될 가능성이 있다. 반면 Accept 헤더를 사용하는 방식은 HTTP 표준에 더 부합하며, 콘텐츠 협상 메커니즘의 일부로 자연스럽게 통합된다.
방식 | 예시 | 장점 | 단점 |
|---|---|---|---|
|
| URI가 깔끔하게 유지됨, 표준 콘텐츠 협상 방식 | 클라이언트가 헤더를 항상 설정해야 함, 브라우저 테스트가 어려울 수 있음 |
사용자 정의 헤더 |
| 구현이 간단함, URI 변화 없음 | 비표준 방식, 캐싱 및 프록시 문제 가능성 |
헤더 방식을 채택할 경우, 서버는 명시된 버전이 없을 때 사용할 기본 버전(예: 최신 안정판 또는 v1)에 대한 정책을 명확히 정의해야 한다. 또한 API 문서화와 클라이언트 SDK에서 버전 지정 방법을 잘 안내하는 것이 중요하다.
REST API의 보안은 인증, 인가, 통신 보안 등 여러 측면을 고려하여 설계되어야 한다. 가장 기본적인 보안 조치는 모든 통신에 HTTPS를 사용하는 것이다. HTTPS는 SSL/TLS 프로토콜을 통해 데이터를 암호화하여 전송 중인 데이터의 기밀성과 무결성을 보장한다. 또한, 민감한 정보를 URI 경로나 쿼리 문자열에 포함시키지 않도록 주의해야 한다.
인증은 사용자나 클라이언트의 신원을 확인하는 과정이다. 일반적으로 OAuth 2.0, JWT, API 키 등을 활용한다. 인가는 인증된 주체가 특정 자원에 접근하거나 작업을 수행할 권한이 있는지 검증하는 과정이다. 역할 기반 접근 제어나 속성 기반 접근 제어 모델을 통해 세밀한 권한 관리를 구현할 수 있다. 인증 및 인가 정보는 주로 Authorization 헤더를 통해 전달된다.
CORS는 다른 출처의 웹 페이지에서 REST API를 안전하게 호출할 수 있도록 제어하는 메커니즘이다. 서버는 Access-Control-Allow-Origin 헤더를 통해 허용할 출처를 명시적으로 지정해야 한다. 신뢰할 수 없는 출처를 무분별하게 허용하는 것은 교차 사이트 요청 위조 등의 보안 위협을 초래할 수 있다. 또한, 요청 빈도 제한, 입력값 검증, 민감한 데이터 마스킹 등의 추가 보안 조치도 필요하다.
인증은 클라이언트의 신원을 확인하는 과정이다. 일반적으로 사용자 이름과 비밀번호, API 키, 또는 JWT와 같은 토큰을 사용하여 서버가 "누구인지"를 검증한다. 반면 인가는 인증된 클라이언트가 특정 자원에 접근하거나 작업을 수행할 권한이 있는지를 결정하는 과정이다. 인증이 신분증을 확인하는 것이라면, 인가는 그 사람이 특정 공간에 들어갈 수 있는 권한이 있는지 확인하는 것에 비유할 수 있다.
REST API에서 널리 사용되는 인증 방식은 OAuth 2.0과 JWT의 조합이다. OAuth 2.0은 인증 서버로부터 액세스 토큰을 발급받는 표준 프로토콜이며, 발급된 토큰은 주로 JWT 형식으로 구성된다. 클라이언트는 이 토큰을 API 요청의 Authorization 헤더에 담아 전송한다[8]. 서버는 토큰의 서명을 검증하여 요청자의 신원과 권한을 확인한다.
인가는 주로 역할 기반 접근 제어 모델을 통해 구현된다. 서버는 토큰에 포함된 클레임 정보를 기반으로 사용자의 역할을 식별하고, 사전에 정의된 정책에 따라 특정 URI나 HTTP 메서드에 대한 접근을 허용하거나 거부한다. 예를 들어, 일반 사용자 역할은 GET /articles 요청은 가능하지만, DELETE /articles/{id} 요청은 불가능하도록 설정할 수 있다.
모든 인증 및 인가 관련 통신은 반드시 HTTPS를 통해 암호화되어야 한다. 이를 통해 토큰이나 자격 증명이 네트워크 상에서 탈취되는 것을 방지할 수 있다. 또한, CORS 정책을 명확히 설정하여 인가되지 않은 출처에서의 API 호출을 사전에 차단하는 것도 보안 강화에 중요하다.
HTTPS는 HTTP 프로토콜에 TLS 암호화 계층을 추가한 보안 통신 프로토콜이다. REST API 설계에서 HTTPS 적용은 선택이 아닌 필수 사항이다. 클라이언트와 서버 간에 주고받는 모든 데이터, 특히 인증 토큰, 사용자 자격 증명, 개인정보 등 민감한 정보는 네트워크 구간에서 도청이나 변조를 방지하기 위해 반드시 암호화되어야 한다. 또한 HTTPS는 통신 상대방의 신원을 확인하여 중간자 공격을 방지하고, 데이터 무결성을 보장한다. 현대의 웹 표준과 브라우저 정책은 점점 더 HTTPS 사용을 강제하는 방향으로 발전하고 있다[9].
CORS(Cross-Origin Resource Sharing)는 웹 브라우저의 동일 출처 정책에 의해 제한되는 교차 출처 HTTP 요청을 안전하게 수행할 수 있도록 하는 메커니즘이다. REST API가 웹 브라우저 클라이언트를 서비스한다면, API 서버와 클라이언트 애플리케이션이 서로 다른 출처(도메인, 프로토콜, 포트)에 존재할 경우 CORS 정책을 반드시 구성해야 한다. 서버는 Access-Control-Allow-Origin과 같은 CORS 관련 HTTP 응답 헤더를 통해, 어떤 출처에서의 요청을 허용할지 명시적으로 선언한다.
CORS 정책 구성은 보안과 기능 사이의 균형을 고려해야 한다. 가장 간단한 설정은 Access-Control-Allow-Origin: *으로 모든 출처를 허용하는 것이지만, 이는 보안상 취약할 수 있다. 가능하다면 신뢰하는 특정 출처만을 명시적으로 허용하는 것이 바람직하다. 또한 Access-Control-Allow-Methods 헤더를 통해 허용할 HTTP 메서드를, Access-Control-Allow-Headers를 통해 허용할 요청 헤더를 제한할 수 있다. 사전 요청은 클라이언트가 본 요청을 보내기 전에 OPTIONS 메서드로 서버의 CORS 정책을 먼저 확인하는 절차이다.
헤더 | 역할 | 예시 값 |
|---|---|---|
| 요청을 허용할 출처 지정 |
|
| 허용할 HTTP 메서드 지정 |
|
| 허용할 요청 헤더 지정 |
|
| 자격 증명 포함 요청 허용 여부 |
|
HTTPS와 CORS는 함께 작동하여 REST API의 통신 채널 자체를 보호하고, 브라우저 기반 클라이언트의 안전한 접근을 가능하게 하는 핵심 보안 층을 형성한다.
성능 최적화는 REST API의 확장성과 사용자 경험을 보장하는 핵심 요소이다. 주요 기법으로는 캐싱, 조건부 요청, 페이징, 필터링 등이 있다.
HTTP 표준은 캐싱을 위한 강력한 메커니즘을 제공한다. Cache-Control 헤더를 사용하여 응답이 클라이언트나 중간 프록시 서버에 얼마나 오래 캐시될 수 있는지 제어할 수 있다. 조건부 요청은 ETag(엔터티 태그)나 Last-Modified 헤더와 함께 If-None-Match, If-Modified-Since 같은 헤더를 활용한다. 이를 통해 클라이언트는 리소스가 변경되었을 때만 새로운 데이터를 받아오고, 변경되지 않았다면 서버는 가벼운 304 Not Modified 응답을 반환하여 대역폭을 절약한다.
대량의 데이터를 다루는 컬렉션 엔드포인트에서는 페이징과 필터링이 필수적이다. 페이징은 limit과 offset 파라미터를 사용하거나, 커서 기반 방식을 통해 데이터를 작은 덩어리로 나누어 제공한다. 이는 네트워크 부하를 줄이고 클라이언트의 처리 속도를 향상시킨다. 필터링(filter), 정렬(sort), 검색(q)과 같은 파라미터를 지원하면 클라이언트가 필요로 하는 정확한 데이터 집합만 효율적으로 조회할 수 있다.
최적화 기법 | 주요 목적 | 구현 예시 |
|---|---|---|
캐싱 | 반복 요청 감소, 응답 속도 향상 |
|
조건부 요청 | 변경된 데이터만 전송, 대역폭 절약 |
|
페이징 | 대량 데이터 처리 부하 분산 |
|
필터링/정렬 | 불필요한 데이터 전송 방지 |
|
캐싱은 REST API의 성능을 크게 향상시키는 핵심 기법이다. 서버 응답을 클라이언트나 중간 프록시 서버에 일시적으로 저장하여, 동일한 자원에 대한 반복적인 요청 시 네트워크 왕복 시간과 서버 부하를 줄인다. 효과적인 캐싱을 위해 서버는 응답에 적절한 HTTP 헤더를 포함시켜야 한다. Cache-Control 헤더를 사용하여 응답을 캐시할 수 있는 최대 시간(max-age)이나 캐시 가능 여부(public, private, no-cache)를 지시한다. ETag(엔티티 태그)나 Last-Modified 헤더는 자원의 버전이나 최종 수정 시간을 제공하여, 이후의 조건부 요청을 가능하게 한다.
조건부 요청은 캐싱 메커니즘과 함께 작동하여 네트워크 대역폭을 추가로 절약한다. 클라이언트가 이전에 받은 응답의 ETag 값을 If-None-Match 헤더에 담아 요청하거나, Last-Modified 값을 If-Modified-Since 헤더에 담아 요청한다. 서버는 이 조건을 검사하여 자원이 변경되지 않았다면 내용 본문 없이 304 Not Modified 상태 코드만 반환한다. 이를 통해 불필요한 데이터 전송을 방지한다.
다음은 주요 캐싱 관련 HTTP 헤더와 그 역할을 정리한 표이다.
헤더 | 방향 | 설명 및 주요 지시자 |
|---|---|---|
| 응답/요청 | 응답의 캐시 정책을 정의한다. |
| 응답 | 자원의 버전을 식별하는 불변의 문자열 값이다. "W/" 접두사는 약한 검사기(Weak Validator)를 의미한다. |
| 응답 | 서버가 자원을 마지막으로 수정한 날짜와 시간이다. |
| 요청 | 클라이언트가 가진 |
| 요청 | 클라이언트가 가진 |
캐싱 전략은 자원의 특성에 따라 달라져야 한다. 거의 변하지 않는 정적 자원(예: 프로필 이미지, 문서 파일)은 Cache-Control: public, max-age=31536000과 같이 긴 유효 시간을 설정할 수 있다. 반면, 실시간 정보나 사용자별 데이터는 Cache-Control: private, no-cache 또는 짧은 max-age 값을 사용하여 신선하지 않은 데이터가 제공되는 것을 방지한다. 올바른 캐싱 정책은 API 성능과 데이터 일관성 사이의 균형을 이룬다.
대량의 데이터를 효율적으로 처리하고 네트워크 부하를 줄이기 위해 페이징과 필터링은 REST API 설계의 필수적인 기법이다. 이 기법들은 클라이언트가 필요한 데이터만 요청하고, 서버가 과도한 부하 없이 응답할 수 있도록 돕는다.
페이징은 결과 집합을 작은 덩어리(페이지)로 나누어 반환하는 메커니즘이다. 일반적으로 limit과 offset 또는 page와 size와 같은 쿼리 파라미터를 사용하여 구현한다. 예를 들어, /api/users?page=2&size=20은 20개 항목씩 나눈 두 번째 페이지의 사용자 목록을 요청한다. 응답에는 현재 페이지 데이터와 함께 전체 항목 수, 총 페이지 수, 다음 또는 이전 페이지의 URI를 나타내는 메타데이터를 포함하는 것이 좋다. 이를 통해 클라이언트가 추가 탐색을 쉽게 할 수 있다. 일관된 파라미터 명칭(예: page, size)을 사용하는 것이 중요하다.
필터링은 클라이언트가 특정 조건을 만족하는 데이터만 요청할 수 있도록 한다. 쿼리 파라미터를 사용하여 기준을 전달하는 것이 일반적이다. 예를 들어, /api/products?category=electronics&minPrice=100은 '전자제품' 카테고리에서 최소 가격이 100 이상인 상품만 조회한다. 정렬(Sorting)도 함께 적용될 수 있으며, sort 파라미터(예: sort=price,desc)로 제어한다. 필터링과 정렬 기준은 API 문서에 명확히 정의해야 한다.
페이징과 필터링은 종종 결합되어 사용된다. 아래는 일반적인 구현 패턴을 보여주는 예시이다.
쿼리 파라미터 | 설명 | 예시 |
|---|---|---|
| 요청하는 페이지 번호 (1부터 시작) |
|
| 한 페이지당 항목 수 |
|
| 필터링 기준 (필드명=값) |
|
| 정렬 기준 (필드명,방향) |
|
효율적인 구현을 위해 서버 측에서는 데이터베이스 쿼리에 LIMIT, OFFSET 또는 커서 기반 페이징을 적용하고, 적절한 인덱스를 활용하여 성능 저하를 방지해야 한다.
클라이언트가 잘못된 요청을 보내거나 서버 내부에 문제가 발생했을 때, 일관되고 명확한 오류 응답을 제공하는 것은 REST API의 사용성과 디버깅 효율성을 크게 높인다. 오류 응답의 본문은 항상 구조화된 형식(주로 JSON)으로 반환하며, 최소한 오류 코드, 메시지, 관련 리소스에 대한 링크 등을 포함해야 한다. 모든 오류 케이스가 HTTP 상태 코드만으로 충분히 설명되지 않을 수 있으므로, 응답 본문에 구체적인 정보를 추가하는 것이 좋다.
일관된 오류 응답 형식은 개발자가 오류를 빠르게 이해하고 처리하는 데 도움을 준다. 일반적인 구조는 다음과 같다.
필드 | 설명 | 예시 |
|---|---|---|
| 애플리케이션 수준의 오류 식별 코드 |
|
| 개발자나 최종 사용자를 위한 이해하기 쉬운 오류 설명 |
|
| (선택) 오류에 대한 추가적인 세부 정보 |
|
| 오류가 발생한 시간 |
|
상세 오류 정보를 제공하면 클라이언트가 문제를 정확히 진단하고 수정하는 것이 가능해진다. 예를 들어, 입력값 검증 오류의 경우 detail 필드에 어떤 필드가 어떤 이유로 유효하지 않은지 목록을 포함할 수 있다. 또한, 관련 API 문서나 도움말 페이지로 연결되는 help_url 링크를 포함하는 것도 유용한 방법이다. 서버 내부 오류(예: 5xx 상태 코드)에 대해서는 보안상의 이유로 스택 트레이스와 같은 민감한 정보를 노출해서는 안 되지만, 로깅을 위한 고유한 추적 ID(trace_id)를 제공하여 지원 팀이 문제를 조사할 수 있도록 할 수 있다.
클라이언트가 오류를 쉽게 파악하고 처리할 수 있도록, 모든 오류 응답은 일관된 구조를 가져야 한다. 일반적으로 JSON 형식의 응답 본문에 오류의 유형, 메시지, 상세 정보, 그리고 필요에 따라 오류 코드나 관련 리소스에 대한 참조를 포함한다. 공통적인 필드로는 error, message, status, timestamp 등이 사용된다.
다음은 일관된 오류 응답 형식의 예시이다.
필드명 | 데이터 타입 | 설명 | 예시 |
|---|---|---|---|
| 문자열(ISO 8601) | 오류가 발생한 시각 | "2023-10-27T10:30:00Z" |
| 정수 | 404 | |
| 문자열 | 오류의 일반적인 분류 | "Not Found" |
| 문자열 | 개발자나 최종 사용자를 위한 설명 메시지 | "요청한 사용자 ID를 찾을 수 없습니다." |
| 문자열 | 오류를 발생시킨 요청 URI | "/api/v1/users/999" |
| 객체 (선택사항) | 추가적인 검증 오류나 디버깅 정보 |
|
이러한 구조를 따르면, 클라이언트 측에서는 응답의 status 필드를 확인하는 것만으로도 오류의 대략적인 원인을 파악할 수 있고, message와 details를 통해 구체적인 조치를 취할 수 있다. 또한, 모든 API 엔드포인트에서 동일한 형식으로 오류를 반환함으로써 클라이언트의 오류 처리 로직을 표준화하고 유지보수성을 높일 수 있다.
클라이언트가 오류의 원인을 정확히 이해하고 적절히 대응할 수 있도록, 오류 응답에는 충분한 상세 정보가 포함되어야 한다. 일반적인 HTTP 상태 코드만으로는 문제의 구체적인 맥락을 전달하기 어렵기 때문이다.
일관된 오류 응답 형식을 바탕으로, 다음과 같은 상세 정보를 제공하는 것이 바람직하다.
code: 내부적으로 정의된 애플리케이션별 오류 코드. 같은 400 오류라도 "잘못된 이메일 형식"과 "필수 필드 누락"을 구분하는 데 사용된다.
message: 오류 상황을 요약한, 사람이 읽을 수 있는 설명 메시지이다.
target: 오류가 발생한 특정 필드나 파라미터, 엔드포인트를 가리킨다. 예를 들어 "target": "email"과 같이 표시한다.
details: 중첩된 오류 객체의 배열로, 입력 유효성 검사 실패 시 여러 필드의 오류를 한 번에 보고할 때 유용하다.
timestamp: 오류가 발생한 시점을 기록한다. 이는 문제 추적과 디버깅에 도움이 된다.
이러한 상세 정보는 클라이언트 개발자의 디버깅을 용이하게 하고, 최종 사용자에게는 더 명확한 안내 메시지를 표시하는 데 활용될 수 있다. 그러나 보안을 고려하여 내부 시스템 구조나 민감한 정보(예: 스택 트레이스, 데이터베이스 쿼리)는 프로덕션 환경에서는 노출해서는 안 된다[10]. 오류 응답의 상세 수준은 애플리케이션의 요구사항과 보안 정책에 따라 조정되어야 한다.