JSON:API 완벽 가이드: JSON API 스펙 완전 정복

Rebecca Kovács

Rebecca Kovács

19 May 2025

JSON:API 완벽 가이드: JSON API 스펙 완전 정복

REST (Representational State Transfer)는 웹 서비스를 구축하기 위한 기본적인 아키텍처 스타일을 제공합니다. 하지만 요청 및 응답 형식의 많은 측면을 정의하지 않은 채로 남겨둡니다. 이러한 모호성은 불일치, 개발 오버헤드 증가, API 소비자의 학습 곡선 가파름으로 이어질 수 있습니다. 이때 JSON:API가 등장합니다. JSON:API는 JSON으로 API를 구축하기 위한 표준화되고 컨벤션 기반의 접근 방식을 제공하는 사양입니다.

이 종합 가이드는 JSON:API 사양에 대해 깊이 있게 탐구하며 핵심 개념, 구조 및 강력한 기능을 살펴봅니다. 리소스 가져오기, 생성, 업데이트, 삭제, 관계 관리, 오류 처리 및 데이터 전송 최적화를 위한 메커니즘을 분석하여 강력하고 효율적인 API를 설계하고 사용하는 데 필요한 지식을 갖추도록 지원합니다.

💡
멋진 API 문서를 생성하는 훌륭한 API 테스트 도구를 원하십니까?

최대 생산성으로 개발 팀이 함께 작업할 수 있는 통합 올인원 플랫폼을 원하십니까?

Apidog는 귀하의 모든 요구를 충족하며 훨씬 저렴한 가격으로 Postman을 대체합니다!
button

JSON:API를 사용해야 하는 이유: 설명

기술적인 복잡성에 들어가기 전에 JSON:API가 해결하고자 하는 문제를 이해하는 것이 중요합니다. 공유된 컨벤션이 없으면 API 개발자는 종종 상당한 시간을 들여 다음 사항에 대해 논쟁합니다.

JSON:API는 요청 및 응답에 대한 명확하고 일관된 형식을 정의함으로써 이러한 문제를 해결합니다. 이러한 표준화는 몇 가지 주요 이점을 제공합니다.

핵심 개념: JSON:API 문서의 구성 요소

JSON:API의 핵심은 리소스(resources) 개념을 중심으로 돌아갑니다. 리소스는 "아티클", "사용자" 또는 "제품"과 같은 특정 유형의 개별 레코드입니다. 요청이든 응답이든 모든 JSON:API 문서는 특정 구조를 따릅니다.

문서 구조: 최상위 멤버

JSON:API 문서는 다음 최상위 멤버 중 하나 이상을 반드시 포함해야 하는 JSON 객체입니다.

또한 문서는 다음 최상위 멤버를 포함할 수도 있습니다.

리소스 객체: 데이터 표현

리소스 객체는 JSON:API의 초석이며 다음을 반드시 포함해야 합니다.

리소스 객체는 다음을 포함할 수도 있습니다.

리소스 객체 예시:JSON

{
  "type": "articles",
  "id": "1",
  "attributes": {
    "title": "JSON:API Unveiled",
    "body": "A deep dive into the specification...",
    "created_at": "2025-05-15T10:00:00Z",
    "updated_at": "2025-05-16T14:30:00Z"
  },
  "relationships": {
    "author": {
      "links": {
        "self": "/articles/1/relationships/author",
        "related": "/articles/1/author"
      },
      "data": { "type": "users", "id": "42" }
    },
    "comments": {
      "links": {
        "self": "/articles/1/relationships/comments",
        "related": "/articles/1/comments"
      },
      "data": [
        { "type": "comments", "id": "5" },
        { "type": "comments", "id": "12" }
      ]
    }
  },
  "links": {
    "self": "/articles/1"
  }
}

리소스 식별자 객체

리소스 식별자 객체는 typeid만 포함하는 리소스의 최소 표현입니다. 전체 리소스 객체를 포함하지 않고 다른 리소스로 연결하기 위해 관계 객체 내에서 사용됩니다.

리소스 식별자 객체 예시:JSON

{ "type": "users", "id": "42" }

링크 객체

링크 객체는 API 탐색을 위한 URL을 제공합니다. 일반적인 링크 멤버는 다음과 같습니다.

링크는 다음과 같이 표현될 수 있습니다.

링크 객체 예시 (관계 내):JSON

"links": {
  "self": "http://example.com/articles/1/relationships/author",
  "related": "http://example.com/articles/1/author"
}

메타 객체

메타 객체는 비표준 메타 정보를 포함할 수 있도록 합니다. 이는 임의의 키-값 쌍이 될 수 있습니다. 예를 들어, meta 객체는 데이터와 관련된 저작권 정보 또는 타임스탬프를 포함할 수 있습니다.

메타 객체 예시:JSON

"meta": {
  "copyright": "Copyright 2025 Example Corp.",
  "authors": ["John Doe"]
}

콘텐츠 협상: 올바른 언어로 소통하기

JSON:API는 자체 미디어 유형인 application/vnd.api+json을 정의합니다.

서버는 표준 콘텐츠 협상을 통해 application/vnd.api+json과 함께 다른 미디어 유형을 지원할 수 있습니다.

데이터 가져오기: 리소스 및 컬렉션 검색

JSON:API는 클라이언트가 필요에 따라 데이터를 정확하게 검색할 수 있는 강력한 메커니즘을 제공합니다.

개별 리소스 가져오기

단일 리소스를 가져오려면 클라이언트는 해당 리소스를 나타내는 엔드포인트에 GET 요청을 보냅니다.

요청:

GET /articles/1

Accept: application/vnd.api+json

성공적인 응답 (200 OK):JSON

{
  "links": {
    "self": "/articles/1"
  },
  "data": {
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON:API Rocks!"
    }
    // ... other attributes and relationships
  }
}

리소스가 존재하지 않는 경우 서버는 404 Not Found를 반환해야 합니다. 일대일 관련 리소스 링크를 가져왔는데 관계가 비어 있는 경우 기본 데이터는 null이 됩니다.

리소스 컬렉션 가져오기

리소스 컬렉션을 가져오려면 클라이언트는 해당 컬렉션을 나타내는 엔드포인트에 GET 요청을 보냅니다.

요청:

GET /articles

Accept: application/vnd.api+json

성공적인 응답 (200 OK):JSON

{
  "links": {
    "self": "/articles",
    "next": "/articles?page[offset]=10",
    "last": "/articles?page[offset]=50"
  },
  "data": [
    {
      "type": "articles",
      "id": "1",
      "attributes": { "title": "Article 1" }
      // ...
    },
    {
      "type": "articles",
      "id": "2",
      "attributes": { "title": "Article 2" }
      // ...
    }
    // ... more articles
  ]
}

컬렉션이 비어 있는 경우 data 멤버는 빈 배열 []이 됩니다.

관계: 리소스 연결

관계는 대부분의 데이터 모델에서 기본적인 부분입니다. JSON:API는 관계를 정의하고 상호 작용하는 명확한 방법을 제공합니다.

관계 표현

관계는 리소스의 relationships 객체 내에서 정의됩니다. relationships 객체의 각 항목은 고유한 관계("author", "comments" 등)를 나타냅니다.

관계 객체는 다음 중 하나 이상을 반드시 포함해야 합니다.

"author" (일대일) 및 "comments" (일대다) 관계 예시:JSON

"relationships": {
  "author": {
    "links": {
      "self": "/articles/1/relationships/author",
      "related": "/articles/1/author"
    },
    "data": { "type": "users", "id": "42" }
  },
  "comments": {
    "links": {
      "self": "/articles/1/relationships/comments",
      "related": "/articles/1/comments"
    },
    "data": [
      { "type": "comments", "id": "5" },
      { "type": "comments", "id": "12" }
    ]
  }
}

관계 가져오기

클라이언트는 제공된 링크를 사용하여 관계 자체 또는 관련 리소스에 대한 정보를 가져올 수 있습니다.

관계 연결 가져오기 (self link):

GET /articles/1/relationships/comments

Accept: application/vnd.api+json

이는 아티클 "1"과 관련된 댓글에 대한 리소스 식별자 객체 컬렉션을 반환합니다.

관련 리소스 가져오기 (related link):

GET /articles/1/comments

Accept: application/vnd.api+json

이는 아티클 "1"과 관련된 전체 댓글 리소스 객체 컬렉션을 반환합니다.

데이터 검색 최적화

JSON:API는 대역폭을 최소화하고 클라이언트 측 성능을 향상시키기 위해 데이터 검색 방법을 최적화하는 여러 기능을 제공합니다.

복합 문서: include를 사용하여 HTTP 요청 줄이기

관련 리소스를 가져오기 위해 서버에 여러 번 왕복하는 것을 방지하기 위해 JSON:API는 클라이언트가 include 쿼리 매개변수를 사용하여 관련 리소스를 기본 응답에 포함하도록 요청할 수 있도록 합니다. 그러면 서버는 이러한 리소스를 최상위 included 배열에 사이드로딩합니다.

아티클을 가져오고 해당 작성자 및 댓글을 포함하는 요청:

GET /articles/1?include=author,comments

Accept: application/vnd.api+json

응답 (200 OK):JSON

{
  "data": {
    "type": "articles",
    "id": "1",
    "attributes": { "title": "..." },
    "relationships": {
      "author": {
        "data": { "type": "users", "id": "42" }
      },
      "comments": {
        "data": [
          { "type": "comments", "id": "5" },
          { "type": "comments", "id": "12" }
        ]
      }
    }
  },
  "included": [
    {
      "type": "users",
      "id": "42",
      "attributes": { "name": "John Doe" }
    },
    {
      "type": "comments",
      "id": "5",
      "attributes": { "body": "Great article!" }
    },
    {
      "type": "comments",
      "id": "12",
      "attributes": { "body": "Very informative." }
    }
  ]
}

스파스 필드셋: 필요한 필드만 가져오기

클라이언트는 fields[TYPE] 쿼리 매개변수를 사용하여 주어진 유형의 리소스에 대해 특정 필드(속성 및 관계)만 반환되도록 요청할 수 있습니다. 이는 페이로드 크기를 줄입니다.

아티클을 가져오되 제목과 작성자 관계만 가져오는 요청:

GET /articles?fields[articles]=title,author

Accept: application/vnd.api+json

응답 (200 OK):JSON

{
  "data": [
    {
      "type": "articles",
      "id": "1",
      "attributes": {
        "title": "Article 1"
      },
      "relationships": {
        "author": {
          "data": { "type": "users", "id": "42" }
        }
      }
    }
    // ... other articles with only title and author
  ]
}

정렬

클라이언트는 sort 쿼리 매개변수를 사용하여 기본 데이터를 정렬하도록 요청할 수 있습니다.

생성 날짜(내림차순) 및 제목(오름차순)으로 정렬된 아티클을 가져오는 요청:

GET /articles?sort=-created_at,title

Accept: application/vnd.api+json

페이지네이션

JSON:API는 다양한 페이지네이션 전략을 지원합니다. 사양은 최상위 links 객체에 페이지네이션 링크(first, prev, next, last)가 어떻게 나타나야 하는지를 정의합니다. 실제 페이지네이션 전략(예: 페이지 기반, 오프셋 기반, 커서 기반)은 page[number], page[size], page[offset], page[limit] 또는 page[cursor]와 같은 쿼리 매개변수를 사용하여 서버에 의해 결정됩니다.

페이지 기반 페이지네이션 링크 예시:JSON

"links": {
  "self": "/articles?page[number]=2&page[size]=10",
  "first": "/articles?page[number]=1&page[size]=10",
  "prev": "/articles?page[number]=1&page[size]=10",
  "next": "/articles?page[number]=3&page[size]=10",
  "last": "/articles?page[number]=5&page[size]=10"
}

클라이언트는 자체 페이지네이션 URL을 구성하는 대신 제공된 링크를 사용해야 합니다.

필터링

사양은 데이터 필터링을 위해 filter 쿼리 매개변수를 예약합니다. 하지만 특정 필터링 전략을 의무화하지는 않습니다. 서버는 filter[attribute]=value와 같은 전략 또는 더 복잡한 표현식 기반 필터링을 구현할 수 있습니다.

예시 (JSON:API에서 권장하지만 의무 사항은 아님):

GET /comments?filter[post]=1 (ID가 1인 게시물의 댓글 가져오기)

GET /comments?filter[post]=1,2&filter[author]=12 (게시물 1 또는 2의 댓글을 작성자 12가 작성한 것으로 가져오기)

클라이언트는 API의 문서를 참조하여 특정 필터링 기능을 이해해야 합니다.

데이터 수정: 리소스 생성, 업데이트 및 삭제

JSON:API는 데이터 조작 작업에 대한 명확한 프로토콜을 정의합니다.

리소스 생성

리소스를 생성하려면 클라이언트는 리소스 컬렉션을 나타내는 URL에 POST 요청을 보냅니다. 요청 본문은 type과 선택적으로 attributesrelationships를 포함하는 단일 리소스 객체를 반드시 포함해야 합니다. 클라이언트는 새 리소스에 대한 id를 제공해서는 안 됩니다 (클라이언트 생성 ID가 지원되고 활성화된 경우는 제외).

요청:

POST /articles

Accept: application/vnd.api+json

Content-Type: application/vnd.api+jsonJSON

{
  "data": {
    "type": "articles",
    "attributes": {
      "title": "New Article Title",
      "body": "Content of the new article."
    },
    "relationships": {
      "author": {
        "data": { "type": "users", "id": "42" }
      }
    }
  }
}

성공적인 응답:

이미 존재하는 클라이언트 생성 ID로 리소스를 생성하려는 시도가 있고 서버가 POST를 통한 업데이트를 지원하지 않는 경우, 서버는 409 Conflict반드시 반환해야 합니다.

리소스 업데이트

리소스는 PATCH HTTP 메서드를 사용하여 업데이트됩니다. 요청에는 업데이트할 리소스의 id반드시 포함되어야 합니다. 요청 본문에는 type, id, 그리고 업데이트할 attributes 및/또는 relationships를 포함하는 리소스 객체가 포함됩니다.

아티클의 제목과 관계 중 하나를 업데이트하는 요청:

PATCH /articles/1

Accept: application/vnd.api+json

Content-Type: application/vnd.api+jsonJSON

{
  "data": {
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "Updated Article Title"
    },
    "relationships": {
      "tags": {
        "data": [
          { "type": "tags", "id": "3" },
          { "type": "tags", "id": "4" }
        ]
      }
    }
  }
}

업데이트의 주요 사항:

성공적인 응답:

업데이트할 리소스가 존재하지 않는 경우 서버는 404 Not Found반드시 반환해야 합니다.

관계를 직접 업데이트하기

JSON:API는 기본 리소스의 속성에 영향을 주지 않고 관계를 관리하는 특정 방법을 제공합니다.

{
  "data": { "type": "users", "id": "24" } // Assign new author or null to clear
}
{
  "data": [
    { "type": "comments", "id": "101" },
    { "type": "comments", "id": "102" }
  ]
}
{
  "data": [
    { "type": "comments", "id": "103" }
  ]
}
{
  "data": [
    { "type": "comments", "id": "5" }
  ]
}

관계 업데이트가 성공하면 일반적으로 200 OK 또는 204 No Content를 반환합니다.

리소스 삭제

리소스를 삭제하려면 클라이언트는 해당 리소스의 엔드포인트에 DELETE 요청을 보냅니다.

요청:

DELETE /articles/1

Accept: application/vnd.api+json

성공적인 응답:

리소스가 존재하지 않는 경우 서버는 404 Not Found를 반환해야 합니다. 사양은 삭제 시 관련 리소스 또는 관계가 어떻게 처리되어야 하는지(예: 연쇄 삭제) 지시하지 않습니다. 이는 구현 세부 사항입니다.

오류 처리

오류가 발생하면 서버는 적절한 HTTP 상태 코드(클라이언트 오류의 경우 4xx, 서버 오류의 경우 5xx)를 반드시 사용해야 합니다. 응답 본문은 JSON:API 오류 문서를 포함해야 합니다.

오류 문서는 오류 객체 배열인 최상위 errors 멤버를 포함합니다. 각 오류 객체는 다음을 포함할 수 있습니다.

오류 응답 예시 (422 Unprocessable Entity):JSON

{
  "errors": [
    {
      "status": "422",
      "source": { "pointer": "/data/attributes/email" },
      "title": "Invalid Attribute",
      "detail": "The email address is not valid."
    },
    {
      "status": "422",
      "source": { "pointer": "/data/relationships/author" },
      "title": "Invalid Relationship Value",
      "detail": "Author with ID '999' does not exist."
    }
  ]
}

서버 측 고려 사항 및 모범 사례

핵심 사양은 포괄적이지만 JSON:API는 URL 설계 및 멤버 명명과 같은 측면에 대한 권장 사항도 제공합니다.

URL 설계

멤버 명명

사양 확장: 확장 기능 및 프로필

JSON:API는 진화하는 요구 사항과 특정 사용 사례를 충족하도록 확장 가능하게 설계되었습니다.

확장 기능

확장 기능은 기본 사양에서 다루지 않는 새로운 기능을 도입할 수 있습니다. 예를 들어, "Atomic Operations" 확장 기능은 단일 원자적 요청으로 여러 작업(생성, 업데이트, 삭제)을 수행할 수 있도록 합니다. 클라이언트와 서버 모두 확장 기능을 사용하려면 이해해야 합니다. 서버가 지원되지 않는 확장 기능을 포함하는 요청을 받으면 적절한 오류로 반드시 응답해야 합니다.

프로필

프로필은 특정 사용 사례(예: 타임스탬프를 처리하는 특정 방법 또는 일반적인 meta 속성 집합)에 대해 기본 사양 위에 일련의 컨벤션을 정의합니다. 확장 기능과 달리 프로필은 한쪽에서 이해하지 못해도 안전하게 무시할 수 있습니다. 이는 핵심 사양을 변경하거나 보편적인 지원을 의무화하지 않고도 일반적인 패턴에 대한 상호 운용성을 촉진하기 위한 것입니다.

서버는 최상위 jsonapi 객체에서 지원되는 확장 기능 및 프로필을 광고할 수 있습니다. 이를 통해 클라이언트는 이러한 기능을 검색하고 그에 따라 요청을 조정할 수 있습니다.

JSON:API의 미래

JSON:API는 커뮤니티의 기여와 새로운 API 설계 과제를 해결해야 하는 필요성에 의해 계속 발전하고 있습니다. 설정보다 컨벤션, 효율성 및 개발자 경험에 대한 초점은 최신 API 구축을 위한 선도적인 표준으로서의 입지를 굳혔습니다. JSON:API를 채택함으로써 개발 팀은 모호성을 크게 줄이고 상호 운용성을 향상시키며 API 개발 및 소비 속도를 가속화할 수 있습니다.

이 상세한 탐구는 JSON:API 사양의 대부분을 다룹니다. 이러한 원칙을 이해하고 구현함으로써 개발자는 기능적일 뿐만 아니라 깔끔하고 일관적이며 작업하기 즐거운 API를 만들 수 있으며, 궁극적으로 보다 생산적이고 협력적인 API 생태계를 조성할 수 있습니다.

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요