Introduction to the JSON API

Published on by

Introduction to the JSON API image

JSON API was originally drafted in May 2013 by Yehuda Katz and reached stable in May 2015, and it is about making your API calls efficient. You can fetch data as you need, adding or removing attributes or relations as your requirements change. This minimizes the amount of data and round trips needed when making API calls.

{json:api}

JSON API documents work as any other API format, you send a request to an endpoint and receive your document. The JSON API specification defines how resources are structured. This structure helps in normalizing how you consume the API.

For example when you make a call to the resource articles through a simple GET request.

GET /articles HTTP/1.1
Accept: application/vnd.api+json

You receive a response which should look something like this:

// ...
{
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": {
"type": "people",
"id": "9"
}
}
},
"links": {
"self": "http://example.com/articles/1"
}
} // ...

Seems simple enough. The author relationship includes a link to the relationship itself and some basic information about the relation. Using the links in the document you can retrieve information on the related resources.

But this is only scratching the surface. There are a quite a few features which make JSON API so lovely to use.

Compound documents

To reduce the number of HTTP requests, servers MAY allow responses that include related resources along with the requested primary resources. Such responses are called “compound documents”.

A compound document is a resource which includes the data of included relations. For example, when requesting an article as shown earlier it may include the data of the author so you don’t need a second call to fetch the author of the article.

This is awesome for a few reasons. Getting the full data of a resource you want to consume in one go is unheard of unless specific endpoints are programmed. On the server side of things, caching and invalidating a request like this is easy.

Just have a look at this example which returns a set of 1 document.

{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
},
"links": {
"self": "http://example.com/articles/1"
},
"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"
}]
}
}
}],
"included": [{
"type": "people",
"id": "9",
"attributes": {
"first-name": "Dan",
"last-name": "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"
}
}]
}

If you look at the included property you see all the related resources for the article. Every included document has a type property which defines which type of resource is returned and a link to the endpoint for the document.

It might feel weird to refer to the author relation in the article only by id and type and loading the related people resource from the included tag. But imagine when there are multiple articles referring to the same author, the document only includes the referred people once in the included data. Pretty efficient!

Inclusion of related resources

In the above example, the server includes all relations in the response, but you might not want this by default since this bloats the responses a bit. For this reason, the specification provides a way to specify what relations should be included in the response.

For example, if you only need the author relationship included you can just call the endpoint with the include request parameter. This will tell the server to only send the specified relation with the response.

GET /articles/1?include=author HTTP/1.1
Accept: application/vnd.api+json

If you need multiple relationships you separate the relations with a comma (,). When defining the includes you can go even further and include nested relations. For example, you want comments, with the authors of the comments you can just include comments.author:

GET /articles/1?include=author,comments.author HTTP/1.1
Accept: application/vnd.api+json

This flexibility makes fetching the correct resources a breeze allowing you to tweak the results as needed.

Sparse fieldsets

When you are using compound documents your request could get large, fast. Especially when the included relationships contain a lot of data. Most of the time you do not need every single attribute defined in the resources but only want things like author names. JSON API provides sparse fieldsets for this use case.

You can specify the fields that are fetched by setting the fields request parameter. The format is fields[TYPE], so you can specify per resource type what fields you need.

GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1
Accept: application/vnd.api+json

This call could include the title and body field from the articles resource and the name field from the people resource.

Other features

Servers may implement a few more features which are defined by the specification. These features are includes, sorting, pagination and filtering.

Sorting

If the server supports it, you can sort your records using the sort request parameter. Sorting is in ascending order by default, to sort descending you can prepend - to the field.

GET /articles?sort=-created,title HTTP/1.1
Accept: application/vnd.api+json

Pagination

If the server supports pagination it will supply a few extra meta attributes with pagination information. The server can decide how to paginate themselves, be it through an offset, or direct page numbers. An example of an implementation uses the required links to other pages and includes some metadata in the meta tag.

{
links: {
first: "http://example.com/articles?page=1",
last: "http://example.com/articles?page=262",
prev: "http://example.com/articles?page=261",
next: null
},
meta: {
current_page: 262,
from: 3916,
last_page: 262,
per_page: 15,
to: 3924,
total: 3924
}
}

Filtering

The spec says little about filtering, it just states there may be a parameter which is called filter which is used to fulfil filtering on the server. The implementation is specific for the server and can be anything.

Creating, Updating and Deleting resources

When you understand the structure of documents, creating and updating resources is a breeze. It uses the standard HTTP verbs to communicate the desired action of the request. GET for fetching, POST for creating, PATCH for (partial) updating and DELETE for deleting resources.

Creating resources

POST /photos HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
 
{
"data": {
"type": "photos",
"attributes": {
"title": "Ember Hamster",
"src": "http://example.com/images/productivity.png"
},
"relationships": {
"photographer": {
"data": { "type": "people", "id": "9" }
}
}
}
}

This is an example of posting a new photo. As you notice the relationship is included in the request in the relationships property.

Updating resources

PATCH /articles/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
 
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "To TDD or Not"
}
}
}

When updating a resource it will update the posted fields to the new values. Values that are not included in the request will not be updated.

Updating relationships

It is possible to update relationships in 2 ways. One way is to include the relation in the PATCH request. The same way as you have seen earlier. Another way is using the specific relationship endpoint.

PATCH /articles/1/relationships/author HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
 
{
"data": { "type": "people", "id": "12" }
}

This call will update the to-one relationship on the article. If you want to delete the relation, you can pass null as the data.

If you want to update a to-many relation you can just send an array of relations to the endpoint. This will replace all members in the relation with the data you post.

PATCH /articles/1/relationships/tags HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
 
{
"data": [
{ "type": "tags", "id": "2" },
{ "type": "tags", "id": "3" }
]
}

When clearing a to-many relation, just send an empty array as data.

Deleting resources

There isn’t much to say about deleting resources, you just send a DELETEcall to the endpoint.

DELETE /photos/1 HTTP/1.1
Accept: application/vnd.api+json

Learn more

If you want to learn more about the details of {json:api} and its usage, check out jsonapi.org. Their specification is quite clear, although there are a lot of features that are not mandatory for servers to implement. On their site, they also maintain a list of client and server implementations for you to get up and running quickly.

They are currently working on version 1.1 of the specification. It will be 100% backwards compatible with version 1.0, only adding new features, breaking nothing.

{json:api} <3

Hopefully, you’ve learned what makes this specification awesome. Personally, I love the structure, consistency and flexibility, this makes communicating with an API a lot easier. You always know what to expect, and don’t need loads of requests to get your data.

For this reason we have been working on a Laravel package for mapping remote {json:api} resources to Eloquent like models and collections, and a package to generate an {json:api} from your Eloquent models.

Björn Brala photo

Technical director at SWIS, on a quest to make the internet a better place through open source.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate logo

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate

Build your SaaS application in hours. Out-of-the-box multi-tenancy and seamless Stripe integration. Supports subscriptions and one-time purchases, allowing you to focus on building and creating without repetitive setup tasks.

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
Asymmetric Property Visibility in PHP 8.4 image

Asymmetric Property Visibility in PHP 8.4

Read article
Access Laravel Pulse Data as a JSON API image

Access Laravel Pulse Data as a JSON API

Read article
Laravel Forge adds Statamic Integration image

Laravel Forge adds Statamic Integration

Read article
Transform Data into Type-safe DTOs with this PHP Package image

Transform Data into Type-safe DTOs with this PHP Package

Read article
PHPxWorld - The resurgence of PHP meet-ups with Chris Morrell image

PHPxWorld - The resurgence of PHP meet-ups with Chris Morrell

Read article
Herd Executable Support and Pest 3 Mutation Testing in PhpStorm 2024.3 image

Herd Executable Support and Pest 3 Mutation Testing in PhpStorm 2024.3

Read article