JSON API
최근 API 규격들에 대해서 알아보던 중 JSON API라는 것을 보게 되었다. REST API가 이미 JSON 포맷을 사용하고 있는데 JSON으로 응답하면 그게 JSON API인가 싶었지만 알아보니 꽤 흥미로워서 정리를 해본다.
JSON API
정식 표기는 JSON:API 인듯한 이 프로토콜은 이름 그대로 JSON 형식을** 사용하는 **표준화된 API 규격이다.
2013년에 처음 발의된듯한 해당 프로토콜은 2015년에 v1.0 이 완성되고 2022년에 v1.1이 완성되었다고 한다.
이미 대부분의 웹 API들이 JSON을 사용하고는 있지만 JSON 데이터 자체의 포맷은 개발자들마다 다 다르게 사용하고 있는데 JSON:API는 그 구조와 규칙을 명확하게 정의한 표준을 제시한다.
JSON:API의 구조
일반적인 REST API에서는 서버마다 JSON 응답 형태가 제각각이지만 JSON:API는 아래 항목들을 표준화한다.
- data: 실제 리소스 데이터
- atrributes: 리소스의 속성
- relationships: 다른 리소스와의 관계
- included: 관계에 따라 포함되는 다른 리소스
- meta, links, errors: 부가 정보, 링크, 에러 정보 등
JSON:API 응답 예시
{
"links": {
"self": "http://example.com/articles",
"next": "http://example.com/articles?page[offset]=2",
"last": "http://example.com/articles?page[offset]=10"
},
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
},
"comments": {
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": "http://example.com/articles/1/comments"
},
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
},
"links": {
"self": "http://example.com/articles/1"
}
}],
"included": [{
"type": "people",
"id": "9",
"attributes": {
"firstName": "Dan",
"lastName": "Gebhardt",
"twitter": "dgeb"
},
"links": {
"self": "http://example.com/people/9"
}
}, {
"type": "comments",
"id": "5",
"attributes": {
"body": "First!"
},
"relationships": {
"author": {
"data": { "type": "people", "id": "2" }
}
},
"links": {
"self": "http://example.com/comments/5"
}
}, {
"type": "comments",
"id": "12",
"attributes": {
"body": "I like XML better"
},
"relationships": {
"author": {
"data": { "type": "people", "id": "9" }
}
},
"links": {
"self": "http://example.com/comments/12"
}
}]
}
위 예시는 https://jsonapi.org/ 에서 보여주는 가상의 블로그 응답으로 articles 리소스에 대한 요청 결과다. 기본적으로 타입과 id를 포함하고, article 객체에 대한 속성 정보는 attributes 항목 안에 포함되어 있다.
articles와 연관된 리소스 타입으로는 author와 comments가 있는 것 같은데, 해당 리소스들의 속성은 included 항목 아래에 포함되어 반환되고 있다.
이렇듯 JSON:API는 연관 데이터들의 정보를 포함하여 반환함으로써 클라이언트에서 반복적인 요청을 보내지 않아도 되도록 하고 있으며 리소스에 대한 정보 및 관계 구조도 일관적으로 전달하기 때문에 클라이언트 쪽 구현을 단순화 해준다.
JSON:API의 주요 규칙
JSON:API는 데이터 구조에 대한 정의 뿐만 아니라 API 규격으로서 지켜야 할 규칙들에 대해서도 명세하고 있다.
HTTP 메소드
기본적으로 REST 원칙을 준수하여 요청에 따라 GET, POST, PATCH, DELETE 메소드를 사용하도록 하며, 처리 결과에 따라 사용해야 하는 HTTP Status Code에 대한 사항까지 정의하고 있다.
Content-Type
JSON:API는 자체 MIME 타입을 IANA에 등록하여 항상 application/vdn.api+json
을 사용하도록 하고 있다.
쿼리 파라미터 패밀리
쿼리 파라미터 사용시, 공통 관심사를 갖는 쿼리 파라미터를 하나의 그룹으로 표현하는 쿼리 파라미터 패밀리라는 개념을 사용하도록 제안하고 있다.
예를들어 페이징이 필요한 경우 페이지 번호와 페이지 당 리소스 개수를 별도 파라미터로 분리하는 대신 아래와 같은 구조로 page라는 가족 개념 아래에 하위 속성들을 나타내어 사용한다.
/?page[offset]=0&page[limit]=10
URL 디자인
반드시 따라야하는 규칙은 아니지만 JSON:API 사용시 URL 디자인은 어떻게 하는 것이 좋은지에 대한 지침들이 포함되어 있다. 기본적으로는 url 또한 리소스를 중심으로 설계하여 관계를 표현하도록 추천한다.
// 기본 리소스 경로
GET /articles
GET /articles/1
// 관계 데이터 직접 조회 경로
GET /articles/1/relationships/author
// 포함된 리소스 조회
GET /articles/1/author
// 중첩 관계
GET /articles/1/comments/2
에러 응답
요청을 제대로 처리할 수 없어 에러를 반환해야 할 경우에 대해서도 JSON:API는 규칙을 제시하고 있다.
기본적으로 최상위에 errors 키를 두고 배열로 구조화 된 에러 정보를 포함시키도록 한다.
{
"errors": [
{
"status": "400",
"title": "Invalid Attribute",
"detail": "First name must contain at least three characters."
}
]
}
각 오류 객체는 다음 필드를 선택적으로 포함할 수 있다.
- id: 에러 트래킹을 위한 오류 식별자
- links: 오류 연관 문서 링크
- status: HTTP 상태 코드
- code: 서버 내부에서 정의한 에러 코드
- title: 오류의 짧은 요약
- detail: 오류의 구체적인 설명
- source: 오류가 발생한 원인 위치
- meta: 추가적인 컨텍스트 데이터
페이네이션
가장 처음 보여준 예시에서 나타나듯 페이지네이션 적용 시 links 필드를 사용해서 리소스 페이지네이션 링크를 선택적으로 제공할 수 있다. 페이지네이션 링크 제공 시에는 반드시 self, first, last, prev, next 키를 사용해서 제공해야 한다.
{
"links": {
"self": "http://example.com/articles",
"next": "http://example.com/articles?page[offset]=2",
"last": "http://example.com/articles?page[offset]=10"
}
}
JSON:API의 장단점
장점
- 표준화된 응답 구조: 모든 API가 같은 형태를 가지므로, 클라이언트 구현이 쉽다.
- 불필요한 요청 감소: included 필드로 관련 데이터를 한 번에 전달할 수 있다.
- 관계형 데이터 표현이 용이: relationships를 통해 관계를 명확히 표현한다.
- 에러 처리 일관성: errors 포맷을 통해 구조화된 에러를 전달한다.
단점
- 구조가 복잡하다: 리소스 관계가 복잡하지 않은 간단한 API에는 과할 수 있다.
- 초기 설정 부담: 제시하는 표준에 맞춰 응답을 포맷팅 하기가 부담스러울 수 있다.
- 유연성 제한: 규칙을 모두 준수하며 커스텀 응답 구조를 만들기가 어려울 수 있다.
JSON:API 활용이 도움이 될 수 있는 상황
- 여러 종류의 클라이언트(웹, 모바일, API 클라이언트)가 동일 API를 사용하는 경우
- 관계형 데이터(예: 사용자 ↔ 게시글 ↔ 댓글)가 많은 경우
- 프론트엔드가 GraphQL처럼 관계형 요청을 많이 하는 경우