Resources reference each other: a book has an author, an author has many books. Plain REST gives you two bad defaults — embed everything (bloated responses) or return only IDs and force the client into follow-up requests. That second one is the network N+1 problem:
GET /books → [{ id: 1, authorId: "a1" }, { id: 2, authorId: "a2" }, ...]
GET /authors/a1 → { name: "Fitzgerald" }
GET /authors/a2 → { name: "Orwell" }
...one more round trip per author JSON:API handles this in two parts. First, every resource declares its relationships as type/id pairs plus links to fetch them:
{
"type": "books",
"id": "42",
"attributes": { "title": "The Hobbit" },
"relationships": {
"author": {
"data": { "type": "authors", "id": "7" },
"links": { "related": "/books/42/author" }
}
}
}
Second, the client can ask for related resources in the same response with
include. The result is a compound document: related records arrive in a
top-level included array, each one appearing exactly once no matter how many books
reference it.
{
"data": [
{
"type": "books", "id": "42",
"attributes": { "title": "The Hobbit" },
"relationships": { "author": { "data": { "type": "authors", "id": "7" } } }
}
],
"included": [
{ "type": "authors", "id": "7", "attributes": { "name": "J.R.R. Tolkien" } }
]
}
One round trip, no duplicated author records, and the client stitches records together by
type + id.
Exercise
Code challenge
Resolve a compound document
Given a JSON:API compound document (books in data, their authors in included), resolve each book's author and return { title, author } pairs. Fill in the top-level array the related records arrive in.