Вы находитесь на странице: 1из 64

JSON API By Example

Adolfo Builes
This book is for sale at http://leanpub.com/json-api-by-example

This version was published on 2016-04-29

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.

2015 - 2016 Adolfo Builes


Contents

What you need to know about this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


Which version of JSON API is covered? . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
What do you mean by mini-book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Audience: Backend developers familiar with Ruby on Rails . . . . . . . . . . . . . . . . . 1
Why should I read this book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
How to read this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
What if I dont know Ruby on Rails? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Demo code on GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Reporting Errata - Suggesting Edits. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

Gettings Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

Borrowers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

Representing resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Our first resource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Top-Level Members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Creating a friend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Model validations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Updating a friend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Deleting a friend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Wrapping up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

Working with relationships . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26


Representing articles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Keeping track of loans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Creating loans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Updating relationships . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Wrapping up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

Fetching data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Side-loading related resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Sparse Fieldsets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
CONTENTS

Pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Whats next? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
What you need to know about this
book
This is a mini-book on building applications with JSON API

Which version of JSON API is covered?


This book will follow JSON API version 1.0.

What do you mean by mini-book?


I refer to this as a mini-book because I want to make it short and to the point, to help you get the
basics behind JSON API and to enabled you to use the tools currently available in the Ruby on Rails
world to build them.

Audience: Backend developers familiar with Ruby on


Rails
In order to understand this book, you should have some basic Ruby on Rails knowledge and know
HTTP basics too.

Why should I read this book?


If you want to learn about JSON API this book will walk you through the process. When paired with
Ember 101, you will learn not only how to create backend applications with this spec, but also how
to connect it with a client in JavaScript.
The book is written in an informal style, so bear with me :).

How to read this book


The book should be read sequentially, since every chapter depends on features built or introduced
in previous chapters.
http://jsonapi.org
https://leanpub.com/ember-cli-101

1
What you need to know about this book 2

What if I dont know Ruby on Rails?


Thats fine even though Ruby on Rails developers are the target audience, you will still be able
to learn the basics of JSON-API here.

Demo code on GitHub


The repository borrowers-api contains code for the application built in the book. Every chapter will
be a branch.

Reporting Errata - Suggesting Edits.


You can report issues in the following repository in GitHub JSON API By Example Errata.
Please include the page number, and, if possible, a screenshot, so it will be easier to identify issues.
<3 Adolfo Builes (a.k.a @abuiles).
https://github.com/abuiles/borrowers-api
https://github.com/abuiles/json-api-by-example-errata/
Gettings Started
We are going to build an API-only application that will respond with a payload following the JSON
API specification.
Well be using rails-api a lighter version of Ruby on Rails to create this application; lets start
by installing it:

1 gem install rails-api

Once rails-api is installed, lets run the project generator:

1 rails-api new borrowers-api

This will create a project very similar to the the ones generated by Ruby on Rails. It will differ
slightly, since there are some things that are not required for API-only applications.
If we follow the documentation of rails-api well see the following items mentioned:

Make ApplicationController inherit from ActionController::API instead of ActionController::Base.


As with middleware, this will leave out any ActionController modules that provide function-
ality primarily used by browser applications.
Configure the generators to skip generating views, helpers and assets when you generate a
new resource.

Rails::API has been merged into Rails, and will be available in Rails 5.

Next, we are going to use JSONAPI::Resources or JR, a framework that can be used to create JSON
API applications. This framework was created by Cerebris Corp, and one of its main contributors
is Dan Gebhardt, who is one of the core team writing the specs for JSON API.
Lets add jsonapi-resources to our Gemfile. Well we working with the latest version, which, at
the time of this writing, was 0.7.1.beta1.
https://github.com/rails-api/rails-api
https://github.com/cerebris/jsonapi-resources
http://www.cerebris.com/
https://twitter.com/dgeb

3
Gettings Started 4

1 gem "jsonapi-resources", "~> 0.7.1.beta1"

With that, we have the basics in place to start working on our JSON API.
Borrowers.
The application we are going to build is called Borrowers; it will allow us to keep track of items we
lend to our friends.
The main resources will be friends, articles and loans. We can create as many friends and articles
as we want. Each friend will be able to borrow any number of articles as long as they are available.
Well need to keep track of the status of every article (e.g. borrowed or returned) and well need
the option of adding notes to it.
Lets start to work through the specification by building the endpoints to create, read, update and
delete friends and articles.

5
Representing resources
In the following chapter we are going to cover creating, updating and deleting resources. Instead of
talking about the specification from top to bottom, well start to work in the application and unveil
the details as we go. At the end of this chapter well know how to create, update and delete records,
and well understand how errors can be represented using JSON API.
Lets start by creating our friend model:

rails g model friend first_name:string \


last_name:string email:string twitter:string
rake db:migrate

After defining our first model and running the migration, we can start playing with JR (JSON-
API::Resources).

Our first resource


In JSON API, models are represented using resource objects. Resource objects are JSON objects with
some keys that help us know things about the represented resource, like its id, type, attributes
and relationships.
Using the definition above, a resource object for a friend will probably look something like this:

a friend model represented as a resource object

{
"type": "friends",
"id": "1",
"attributes": {
"first-name": "Toby",
"last-name": "Price",
"email": "toby@ktm.com"
}
}

http://jsonapi.org/format/1.0/#crud
http://jsonapi.org/format/#document-resource-objects

6
Representing resources 7

The type and id fields are required and should be strings. There is an exception for id we can
omit it only if we are creating a new record.
To represent resources using JR, we first need to create a new class inheriting from JSON-
API::Resource. This class will help us expose the attributes and relationships.

We can do that using the generator jsonapi:resource:

rails generate jsonapi:resource friend

Once the resource has been generated, well have a file under app/resources/friend_resource.rb
like this:

Friend resource

1 class FriendResource < JSONAPI::Resource


2 end

Well leave the resource file like that for now. Lets try to do a GET request for our friends and see if
we get any data back. Well begin by starting the server with rails server, and then running the
following command:

curl http://localhost:3000/friends

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Action Controller: Exception caught</title>
<style>
body {
background-color: #FAFAFA;
colo
....

Oh, it looks like we got an exception! The issue is that we havent defined a route for friends;
building APIs with JR is like building any other kind of Ruby on Rails application, so we need to
define a controller and route. Lets do that
Representing resources 8

rails g controller friends

JR offers two ways to help bring together our controllers and resources: we can either inherit
from ResourceController, or use the ActsAsResourceController module. In this book, well be
inheriting from ResourceController.
Lets change the controller to inherit from the JR controller:

app/controllers/friends.js
class FriendsController < JSONAPI::ResourceController
end

With that, we have automatic support for CRUD operations: JR adds the actions index, show, create,
update, and destroy. If we once again try to curl into the endpoint, well notice that it still fails:
the issue is that we need to add the resource to our routes. Lets do that next.

config/routes.rb
Rails.application.routes.draw do
jsonapi_resources :friends
end

Lets curl once more it should work this time:

curl http://localhost:3000/friends
{"data":[]}

We received a JSON response, with the top-level member data and an empty array. This result is
expected, since we dont yet have any friends.

jq
To make the payload easier to read, well be using a tool called jq which is a command-line
JSON processor, it pretty prints the JSON payload and also has a number of helpers that
allow us to more easily navigate JSON output.
For Mac users, jq can be installed using Homebrew. For all other platforms, see
https://stedolan.github.io/jq/download/

https://stedolan.github.io/jq/download/
Representing resources 9

Top-Level Members
Before moving forward, lets talk about top level members. These are the top-level keys returned
back from the server. In the example above, that would be the key data.
The specification mentions the following:

A document MUST contain at least one of the following top-level members:


data: the documents primary data errors: an array of error objects meta: a meta object
that contains non-standard meta-information.

For now, the important member for us will be data, which can include a resource if we are trying
to fetch a single record, or it can be an array of resources if we are trying to fetch more than one
resource.
There are 3 other members that can also appear in the response:

A document MAY contain any of these top-level members:


jsonapi: an object describing the servers implementation links: a links object related to
the primary data. included: an array of resource objects that are related to the primary
data and/or each other (included resources).

From those three, lets focus for now on the member included; its the one well be looking at when
side-loading relationships.
A response with this member will look something like this:

{
"data": {...},
"included": [{...}, {...}]
}

Its important to mention that if the member data is not included in the response, included should
not appear either.
Having talked about top-level members, lets continue with our friend CRUD, and move now to the
create part
http://jsonapi.org/format/#document-top-level
Representing resources 10

Creating a friend
To create a new friend, we are going to follow the section about creating resources.
The specification tell us that we can create new resources by sending a POST request to the URL that
represents a collection of resources, and that it must include a single resource object as primary data;
in our friends resource, this URL would be /friends, and the payload would be a JSON object with
the member data containing a valid friend resource.
Lets start to build our curl request to create a new friend, letting the server guide us on the things
that we need to include. This will also allow us to see how other top-level members are used, and
what needs to be included when doing this type of request.

curl http://localhost:3000/friends -X POST \


--data-binary '' | jq

{
"errors": [
{
"code": 415,
"detail": "All requests that create or update resources must use the 'appl\
ication/vnd.api+json' Content-Type. This request specified 'application/x-www-fo\
rm-urlencoded.'",
"href": null,
"id": null,
"links": null,
"source": null,
"status": "415",
"title": "Unsupported media type"
}
]
}

Using curl, we made a POST request to http://localhost:3000/friends, sending as payload an


empty string using the option --data-binary.
The response was the top-level member errors, with the HTTP status 415, which is used for
unsupported media types meaning that the server didnt recognize our request as JSON API. To fix
that, the clients need to send the Content-Type header with the value application/vnd.api+json.
Other information is included in an error object, but well study the other fields later on in the book.
Lets try again, this time specifying the content type header:
http://jsonapi.org/format/#crud-creating
Representing resources 11

https://git.io/vwtvL

curl http://localhost:3000/friends -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '' | jq

{
"errors": [
{
"title": "Missing Parameter",
"detail": "The required parameter, data, is missing.",
"id": null,
"href": null,
"code": "106",
"source": null,
"links": null,
"status": "400",
"meta": null
}
]
}

Gists with commands


Because of formatting issues with Leanpub, some snippets will include a gist URL where
well be able to find the same command, but with a format better-suited to copying and
pasting it ;).

Great! The request is now being processed by the server, but we are getting an HTTP status code
400 which is used for bad requests specifically with a malformed syntax. Error objects in JSON
API include two codes.
First is the property code, which, in the example above, is 106. This property is use to represent
application-specific error codes, meaning that we define them in our projects to internally represent
a given error. JR has a set of application error codes that can be found in the README section
about error codes. For this specific error, a missing parameter is represented with the code 106:
PARAM_MISSING = 106.

Second is the property status used to represent the HTTP status code of the response. This dont
have to match the value of code, as we can see in this example where the HTTP code is 400, but
the applications code is 106.
https://github.com/cerebris/jsonapi-resources#error-codes
Representing resources 12

Error also include a property called detail that should be a human-readable explanation of the
problem; here it is telling us that the member or parameter data is missing from the payload. As
mentioned previously, JSON API expects a top-level member to be defined. In this scenario that
member would be data, containing the resource we are creating. Lets add data

https://git.io/vwtvm

curl http://localhost:3000/friends -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data": {}}' | jq

{
"errors": [
{
"title": "Missing Parameter",
"detail": "The required parameter, data, is missing.",
"id": null,
"href": null,
"code": "106",
"source": null,
"links": null,
"status": "400",
"meta": null
}
]
}

Even though we added the data member, it keeps saying that is missing this is because we need
to include some data. Lets try once more, this time including the attributes property inside data:

https://git.io/vwtvZ

curl http://localhost:3000/friends -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"attributes": {}}}' | jq

{
"errors": [
{
"title": "Missing Parameter",
"detail": "The required parameter, type, is missing.",
"id": null,
"href": null,
Representing resources 13

"code": "106",
"source": null,
"links": null,
"status": "400",
"meta": null
}
]
}

It now seems to be working, but its telling us that the parameter type is missing; since we are
representing a resource object, we need to send one that is valid. When we talked about resource
objects, we mentioned that they should always include a type and id, but that the id was optional
if we were creating a new record. So lets try that once more including the type:

https://git.io/vwtvC

curl http://localhost:3000/friends -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type":"friends","attributes":{}}}' | jq
{
"data": {
"id": "1",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/1"
}
}
}

This time the request worked, and we created our first friend! Lets check the response.
It has the top-level member data, with the resource object representing a friend. This resource object
has the two required properties id and type, and a third property, called links, which contains a
link object to the resource we just created.
Link objects are used to represent URLs, and they can take two possible forms. In the example above
we have something like this:

http://jsonapi.org/format/#document-resource-objects
http://jsonapi.org/format/#document-links
Representing resources 14

"links": {
"self": "http://localhost:3000/friends/1"
}

Theres a special property, self, followed by the URL. self is a special keyword used to represent
the resource itself. If we curl to that URL, well get back the friend. This same URL can also be used
for update and delete operations.
The other form, is a property followed by a JSON object like this:

"links": {
"related": {
"href": "http://localhost:3000/friends/1/borrowed-articles",
"meta": {
"count": 5
}
}
}

The object should include the property href, containing the URL. Optionally, it can include a meta
object to add info about the given link. In our example, it represents a relationship its telling us
in the meta that the count for the relationships is five.
Weve talked about type, id and links, but where are the users attributes?
Maybe because we didnt add any attribute to our last POST the attributes property was not being
included in the payload? Lets try to create another user, this time adding a first and last name.
Before doing the POST, lets see how the payload for attributes is going to look, and lets men-
tion something that can be confusing, and that is the naming: attributes":{"first-name":
"Cyril","last-name":"Neveu"}.
Back in the days before JSON API existed, a common way to name URLs and properties in JSON
payloads was to use characters between a-z and then use underscores (_) as a word separator: this_-
is_a_url or first_name.
JSON API has a couple of recommendations about naming, trying to standardize how URLs and
member names should be done too and those recommendations are:

Member names SHOULD start and end with the characters a-z (U+0061 to U+007A)
http://jsonapi.org/recommendations/#naming
Representing resources 15

Member names SHOULD contain only the characters a-z (U+0061 to U+007A), 0-9 (U+0030
to U+0039), and the hyphen minus (U+002D HYPHEN-MINUS, -) as separator between
multiple words.

The previous recommendations are easy to understand, the only thing we need to remember and
which differs from our previous mental model is to use the HYPEN-MINUS instead of underscore
(or U+005F LOW LINE) to separate words. Now lets do that POST:

https://git.io/vwtvE
curl http://localhost:3000/friends -X POST \
-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type":"friends","attributes":{"first-name": "Cyril","\
last-name":"Neveu"}}}' | jq

{
"errors": [
{
"title": "Param not allowed",
"detail": "first-name is not allowed.",
"id": null,
"href": null,
"code": "105",
"source": null,
"links": null,
"status": "400",
"meta": null
},
{
"title": "Param not allowed",
"detail": "last-name is not allowed.",
"id": null,
"href": null,
"code": "105",
"source": null,
"links": null,
"status": "400",
"meta": null
}
]
}

We did our post, but we got an HTTP response of 400, with an application code of 105, for PARAM_-
NOT_ALLOWED.
Representing resources 16

JR uses the resource representation under app/resources/friend_resource.rb to guide operations


on the resource. If we want our models to have any attributes, we need to declare them explicitly in
the resource. Lets do that next, adding the attributes that we defined for our friends model:

app/resources/friend_resource.rb

class FriendResource < JSONAPI::Resource


attribute :first_name
attributes :last_name, :email, :twitter
end

We can use attribute if we want to define a particular attribute, or attributes for more than one.
Now lets do the POST once more:

https://git.io/vV7b3

curl http://localhost:3000/friends -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type":"friends","attributes":{"first-name": "Cyril","\
last-name":"Neveu"}}}' | jq

{
"data": {
"id": "2",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/2"
},
"attributes": {
"first-name": "Cyril",
"last-name": "Neveu",
"email": null,
"twitter": null
}
}
}

Great, now it works! But what about those null fields there? Well be fine if people dont have a
Twitter account who wants all that noise in their life but an email? Come on!
Now that the CRUD for friends is working, lets add some validations.
Representing resources 17

Model validations
Lets create a test to verify that we can create a new friend only if it has the first name, last name
and email fields. To do so, well add the following to test/models/friend_test.rb :

test/models/friend_test.rb

require 'test_helper'

class FriendTest < ActiveSupport::TestCase


test "requires first_name" do
friend = Friend.new(last_name: 'Price', email: 'toby@ktm.com')
assert_not friend.save, "Saved friend without first_name"
assert_not_empty friend.errors[:first_name]
end

test "requires last_name" do


friend = Friend.new(first_name: 'Toby', email: 'toby@ktm.com')
assert_not friend.save, "Saved friend without last_name"
assert_not_empty friend.errors[:last_name]
end

test "requires email" do


friend = Friend.new(first_name: 'Toby', last_name: 'Price')
assert_not friend.save, "Saved friend without email"
assert_not_empty friend.errors[:email]
end
end

After running rake test test/models/friend_test.rb well get three failures lets add valida-
tions to take care of them. We can add validations here as we would in a Ruby on Rails model. Open
app/model/friend.rb and add the following:

class Friend < ActiveRecord::Base


validates :first_name, presence: true
validates :email, presence: true
validates :last_name, presence: true
end

Now that our validations are in place, lets try to create a new friend without adding the required
fields:
Representing resources 18

https://git.io/vV7AG

curl http://localhost:3000/friends -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type":"friends","attributes":{}}}' | jq

{
"errors": [
{
"title": "can't be blank",
"detail": "first-name - can't be blank",
"id": null,
"href": null,
"code": "100",
"source": {
"pointer": "/data/attributes/first-name"
},
"links": null,
"status": "422",
"meta": null
},
{
"title": "can't be blank",
"detail": "email - can't be blank",
"id": null,
"href": null,
"code": "100",
"source": {
"pointer": "/data/attributes/email"
},
"links": null,
"status": "422",
"meta": null
},
{
"title": "can't be blank",
"detail": "last-name - can't be blank",
"id": null,
"href": null,
"code": "100",
"source": {
"pointer": "/data/attributes/last-name"
},
Representing resources 19

"links": null,
"status": "422",
"meta": null
}
]
}

This time we got the response we wanted: the HTTP status code of the request was 422, which is
used when there are semantic errors. Fortunately for us, JSON API give us a way to describe these
kinds of issues, helping the people or machines consuming our API.
Looking at the response, most of it looks familiar. But this time there is some new information the
property source which contains a JSON pointer to the attribute associated with the error.
The following error, for example, tell us that first name cant be blank, and additionally points to
the attribute associated with the error /data/attributes/first-name:

{
"code": 100,
"detail": "first-name - can't be blank",
"href": null,
"id": null,
"links": null,
"source": {
"pointer": "/data/attributes/first-name"
},
"status": "422",
"title": "can't be blank"
}

With our validations in place, lets create a new friend. This time well include all required attributes.
It should work
Representing resources 20

https://git.io/vV5TK

curl http://localhost:3000/friends -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type":"friends","attributes":{"first-name": "Jutta","\
last-name":"Kleinschmidt","email":"jutta@dakar.com"}}}' | jq

{
"data": {
"id": "3",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/3"
},
"attributes": {
"first-name": "Jutta",
"last-name": "Kleinschmidt",
"email": "jutta@dakar.com",
"twitter": null
}
}
}

Just like that, we added validations to our model. JR checks automatically for us if the model is valid
if not, it automatically creates the correct response and error objects. Next, lets explore how to
do update operations.

Updating a friend
To update a friend, well be following the JSON API section on updating resources. Well learn
how the update operation works and how to build and update request using curl.
As in previous examples, lets start by building this request from the ground up. The type of HTTP
method used for update operations is PATCH. It should include as primary data a single resource
object in our scenario that would be a friend representation.
Lets update the first friend we created, that didnt have any attributes. To update this friend, we
need to make a PATCH request to the URL specified by the property self included in the links
object. Lets grab the URL by running the following command:

http://jsonapi.org/format/1.0/#crud-updating
Representing resources 21

curl http://localhost:3000/friends | jq '.data[0].links.self'


"http://localhost:3000/friends/1"

In the previous request, we were fetching all friends and then extracting the the self link from the
first result from the documents primary data.
Now that we know the URL to update our friend, lets send the request:

https://git.io/vV5kz

curl -X PATCH http://localhost:3000/friends/1 \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"id":"1", "type":"friends","attributes":{"first-name":\
"Toby","last-name":"Price","email":"toby@ktml.com"}}}' | jq
{
"data": {
"id": "1",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/1"
},
"attributes": {
"first-name": "Toby",
"last-name": "Price",
"email": "toby@ktml.com",
"twitter": null
}
}
}

It worked! Lets try doing a second patch request, but this time well omit all the attributes and see
the result:
Representing resources 22

https://git.io/vV5L2

curl -X PATCH http://localhost:3000/friends/1 \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"id":"1", "type":"friends","attributes":{}}}' | jq

{
"data": {
"id": "1",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/1"
},
"attributes": {
"first-name": "Toby",
"last-name": "Price",
"email": "toby@ktml.com",
"twitter": null
}
}
}

We may have expected to get an error here, since not all required fields were included. If we look at
the response, though, we received our friend with no fields changed: JSON API doesnt require us
to send all the resource attributes when doing an update.
Following the specification, it tell us this:

If a request does not include all of the attributes for a resource, the server MUST interpret
the missing attributes as if they were included with their current values. The server
MUST NOT interpret missing attributes as null values

According to that, the previous operation was only a no-op. Lets try doing an update again to add
the friends Twitter ID:
Representing resources 23

https://git.io/vV5tI

curl -X PATCH http://localhost:3000/friends/1 \


-H 'Content-Type: application/vnd.api+json'\
--data-binary '{"data":{"id":"1", "type":"friends","attributes":{"twitter": "@\
tobyprice87"}}}' | jq
{
"data": {
"id": "1",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/1"
},
"attributes": {
"first-name": "Toby",
"last-name": "Price",
"email": "toby@ktm.com",
"twitter": "@tobyprice87"
}
}
}

As you can see, only the fields included in the attributes were updated. Lets do yet another request,
this time removeing the twitter value and and making a change to the email:

https://git.io/vV5t2

curl -X PATCH http://localhost:3000/friends/1 \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"id":"1", "type":"friends","attributes":{"email":"toby\
@yamaha.com", "twitter": null}}}' | jq
{
"data": {
"id": "1",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/1"
},
"attributes": {
"first-name": "Toby",
"last-name": "Price",
"email": "toby@yamaha.com",
"twitter": null
Representing resources 24

}
}
}

Now lets fire up validations trying to remove the email:

https://git.io/vV5tH
curl -X PATCH http://localhost:3000/friends/1 \
-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"id":"1", "type":"friends","attributes":{"email":null}\
}}' | jq

{
"errors": [
{
"title": "can't be blank",
"detail": "email - can't be blank",
"id": null,
"href": null,
"code": 100,
"source": {
"pointer": "/data/attributes/email"
},
"links": null,
"status": "422"
}
]
}

We received an errors object because we had tried to explicitly set the value for email to null and,
according to our validations, this would not be a valid value for that attribute.

Deleting a friend
Following the JSON API section on deleting resources, well learn how the delete a resource, and
how to build a delete request using curl.
The delete operation is easier than previous requests: we need to hit the resources URL which we
can find in the self link with a DELETE request.
Lets remove the friend we used as an example for the update operations with the URL http://localhost:3000/friends/1
http://jsonapi.org/format/#crud-deleting
Representing resources 25

$ curl http://localhost:3000/friends/1 -X DELETE -i

HTTP/1.1 204 No Content


X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Cache-Control: no-cache
X-Request-Id: 9b33c0cd-f030-4ff5-bd21-f6706b25fb51
X-Runtime: 0.017662
Server: WEBrick/1.3.1 (Ruby/2.3.0/2015-12-25)
Date: Tue, 26 Jan 2016 16:42:00 GMT
Connection: Keep-Alive

Since this request wont return a response body, we added the option -i to include HTTP headers
in the output. In this specific request, the response was HTTP status code 204, returned when the
request was successful, and there is nothing in the body.
If we look at the list of possible responses for delete operations in the specification (deleting
responses), well see that 202 or 200 are valid responses too. The former is used when the request
is acknowledged but the server has to do something else to complete the request. The latter when
we want to include metadata in the response number of friends left, for example ouch!

Wrapping up
In this chapter we learned about CRUD operations in JSON API and how to use JSONAPI::Resources
to start building APIs.
We also covered how to use curl to manually build requests for our APIs. There are a lot of REST
clients out there that can help us build requests; a super useful one for OS X users is Paw.
Next, we are going to create the articles resource and learn how to work with relationships.
http://jsonapi.org/format/#crud-deleting-responses
https://luckymarmot.com/paw
Working with relationships
Since we have a way to keep track of friends, lets add support for articles. Then well create a join
model between friends and articles, so that every time a friend borrows an article, well know about
it, and well be able to add metadata to that particular loan.

Representing articles
Lets start by creating the article model, which has the attributes name and a boolean flag available,
which will let us know if the item can be loaned or not.

rails g model article name:string available:boolean


rake db:migrate

Once the model has been created, well add a presence validation on the name:

app/models/friend.rb

1 class Article < ActiveRecord::Base


2 validates :name, presence: true
3 end

Next, lets create a JSONAPI::Resources resource controller and link it in the router:

rails g jsonapi:resource article


rails g controller articles

Well edit the resource so it includes the attributes:

26
Working with relationships 27

app/resources/article_resource.rb
1 class ArticleResource < JSONAPI::Resource
2 attributes :name, :available
3 end

And lets also add jsonapi_resources :articles to config/routes.rb.


Now we need to create a controller for articles, and inherit from JSONAPI::ResourceController:

app/controllers/articles_controller.rb
class ArticlesController < JSONAPI::ResourceController
end

Lets use curl to verify that our resource is working as expected we can start by keeping keeping
track of our helmets.
Running the following request should create an article:

https://git.io/vwtJZ
curl http://localhost:3000/articles -X POST \
-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type":"friends","attributes":{"name": "AGV helmet","a\
vailable":"true"}}}'

Running the command above should have failed, with the following error:

{
"errors": [
{
"title": "Invalid resource",
"detail": "friends is not a valid resource.",
"id": null,
"href": null,
"code": 101,
"source": null,
"links": null,
"status": "400"
}
]
}
Working with relationships 28

What was the issue here? Looking carefully at the payload, we see that we set the type of the resource
object to friend, while trying to hit the articles endpoint. Its very important that we include the
right type, so lets try again:

https://git.io/vwtJX

curl http://localhost:3000/articles -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type":"articles","attributes":{"name": "AGV helmet","\
available":true}}}' | jq

This time, our request should have worked and the resource should be included if we hit the index
URL.

curl http://localhost:3000/articles | jq
{
"data": [
{
"id": "1",
"type": "articles",
"links": {
"self": "http://localhost:3000/articles/1"
},
"attributes": {
"name": "AGV helmet",
"available": true
}
}
]
}

With that, we have created the new resource article. Now is a good time to put things together, so
we can start lending things to our friends!

Keeping track of loans


Well create a third model to represent loans. This model will belong to a friend and an article, and
well add an attribute called notes as well as a boolean flag to mark an item as returned.
Working with relationships 29

rails g model loan notes:text friend:references article:references returned:bool\


ean
rake db:migrate

By now we should be familiar with the wiring of a resource using JR so lets create the resource,
controller and router entry, without specifying any of the relationships or attributes yet

rails g jsonapi:resource loan


rails g controller loans

If we got the setup right, and added the resource attributes, a router entry and a controller, we should
get some data back when curling into the index URL for loans:

curl http://localhost:3000/loans | jq
{
"data": []
}

Now lets edit our resources to include data about their relationships
First, lets edit the models so we include the relationships. We can start by including the line has_many
:loans in both the friend.rb and article.rb models, since a friend can have many loans, and an
article can be loaned many times.
If we open the model loan.rb, it should look like the following:

app/models/loan.rb

class Loan < ActiveRecord::Base


belongs_to :friend
belongs_to :article

validates :friend, presence: true


validates :article, presence: true
end
Working with relationships 30

The relationships were added automatically for us when we run the generator.
Once our models are wired up, we want to expose the relationships in our JSON API. To do so, we
use a syntax similar to the one we just used, but this time well write the relationships in the resource
classes.
JR has a couple of helper methods to describe relationships. We can use any of the following forms:

Relationships with JR

class BookResource < JSONAPI::Resource

# We can use the method `relationship`

relationship :publisher, to: :one


relationship :author, to: :many

# Or has_many and has_one

has_many :reviewers

# Unlike ActiveRecord, JR uses has one insteadof belongs_to


# to represent belongs_to relationships

has_one :category

end

Next, lets add relationships to the resources. Well add has_many :loans to the article and friend
resources. Then well edit the loan_resource.rb to include the attributes and relationships.
Our loans resource should look like this:

app/resources/loan_resource.rb

class LoanResource < JSONAPI::Resource


attributes :notes, :returned

has_one :article
has_one :friend
end

We are now ready to keep track of things we loan to our friends.


https://github.com/cerebris/jsonapi-resources#relationships
Working with relationships 31

Creating loans
As in previous examples, lets try to do a POST with the minimum data required by JSON API, and
then work from there based on the output.

https://git.io/vwtJy

curl http://localhost:3000/loans -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type":"loans","attributes":{}}}' | jq
{
"errors": [
{
"title": "can't be blank",
"detail": "friend - can't be blank",
"id": null,
"href": null,
"code": 100,
"source": {
"pointer": "/data/relationships/friend"
},
"links": null,
"status": "422"
},
{
"title": "can't be blank",
"detail": "article - can't be blank",
"id": null,
"href": null,
"code": 100,
"source": {
"pointer": "/data/relationships/article"
},
"links": null,
"status": "422"
}
]
}

As expected, this request failed, since we didnt include data about the friend or the article lent.
Resource objects expect us to define their relationships under the attribute relationships. For every
required relationship, we need to include a relationship object.
Working with relationships 32

Relationship objects must contain one of the following keys: links; data; or meta. We are familiar
with links and meta; the links object helps us identify the relationship it can include a link to the
relationship itself as self, or a link to the related resource, which, in the loan example, would be
a friend or an article. The meta object can help us when we want to send metadata about the
relationship.
The data object contains a resource linkage. Linkages can take different forms depending on the
type of the relationship being represented.
If we are working with a to-one relationships, a linkage can be null if not present, or a resource
identifier if present.
Resource identifiers are objects that help us identify an individual resource. They must have the
type and id attributes, and optionally they can include metadata in the key meta.

A resource identifier for a friend with identifier 1 would look like this:

resource identifier

{
"type": "friends",
"id": "1"
}

Resource identifiers can easily be mistaken for resource objects since they have similar keys.
Unlike resource objects, resource identifiers should only include the minimum data to identify
that object: id and type. They should not include other data such as attributes or relationships.
When working with to-many relationships, resource linkage can be an empty array [] if there are
no related items, or an array of resource identifier objects.
With this new information, lets now create a new loan, sending the correct data in the relationships.

http://jsonapi.org/format/1.0/#document-resource-object-relationships
http://jsonapi.org/format/1.0/#document-resource-object-linkage
http://jsonapi.org/format/1.0/#document-resource-identifier-objects
http://jsonapi.org/format/1.0/#document-resource-identifier-objects
http://jsonapi.org/format/1.0/#document-resource-objects
Working with relationships 33

{
"data": {
"type": "loans",
"attributes": {
"notes": "minor scratches in the face shield",
"returned": false
}
"relationships":{
"friend": {
"data":{
"id":"2",
"type": "friends"
}
},
"article": {
"data":{
"id":"1",
"type": "articles"
}
}
}
}
}

The following request contains all of the information required to create a new loan: it has the
attributes notes and returned, and we also included the relationships objects which contain resource
linkages for both friend and articles.

https://git.io/vwtUf

curl http://localhost:3000/loans -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type": "loans","attributes": {"notes": "minor scratch\
es in the face shield","returned": false},"relationships":{"friend": {"data":{"i\
d":"2","type": "friends"}},"article": {"data":{"id":"1","type": "articles"}}}}}'\
| jq
{
"data": {
"id": "1",
"type": "loans",
"links": {
Working with relationships 34

"self": "http://localhost:3000/loans/1"
},
"attributes": {
"notes": "minor scratches in the face shield",
"returned": false
},
"relationships": {
"article": {
"links": {
"self": "http://localhost:3000/loans/1/relationships/article",
"related": "http://localhost:3000/loans/1/article"
}
},
"friend": {
"links": {
"self": "http://localhost:3000/loans/1/relationships/friend",
"related": "http://localhost:3000/loans/1/friend"
}
}
}
}
}

Updating relationships
JSON API give us different ways to update relationships. We can do it through a patch request to the
resource itself, or change the resource linkage in the relationship so that it points to another object.
Lets say we want to change the related friend with the loan above by doing a PATCH to the resource.
The body for such a request could look like this:

{
"data": {
"id": "1",
"type": "loans"
"relationships": {
"friend": {
"data": {
"id": "2",
"type": "friends"
Working with relationships 35

}
}
}
}
}

Here we are including only the data we are interested in updating; for any missing values, the
server should assume that we want the values to stay the same. In this case, we are not changing
the attributes, nor the relationship article.
We can also update relationships independently, through the URL self returned in the links object.
Through this link, we can manipulate the data of the relationship. In this scenario, we do a PATCH but
instead of sending data about the parent resource, we only send the resource linkage. This request
will be curl http://localhost:3000/loans/1/relationships/friend -X PATCH with the body:

{
"data": {
"id": "3",
"type": "friends"
}
}

If we want to remove the relationship, we can do a patch with "data": null. Unfortunately, it wont
work in our app, since we require both friend and article to be present. We can test this out by
removing the requirements for friend or article and then doing the PATCH.
Lets comment the validations on the loan model file so those relationships are not required for now:

class Loan < ActiveRecord::Base


belongs_to :friend
belongs_to :article

# validates :friend, presence: true


# validates :article, presence: true
end

Now lets do the patch with null as the data:


Working with relationships 36

https://git.io/vwtTn

curl http://localhost:3000/loans/1/relationships/friend -X PATCH \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":null}' -i

HTTP/1.1 204 No Content


X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Cache-Control: no-cache
X-Request-Id: dcbfec86-437d-48d6-9c00-95f2f7d9e850
X-Runtime: 0.004556
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Date: Tue, 16 Feb 2016 14:38:05 GMT
Connection: Keep-Alive

The previous request worked, so that particular loan is no longer associated with any user. We can
double-check by doing a GET to the related endpoint:

curl http://localhost:3000/loans/1/relationships/friend | jq
{
"links": {
"self": "http://localhost:3000/loans/1/relationships/friend",
"related": "http://localhost:3000/loans/1/friend"
},
"data": null
}

Lets explore how to associate that loan with a friend, this time doing the update through the
relationship link for the friend.
If we have a resource object for a friend, we can find the relationship link in the property
.data.relationships.loans.links.self.

curl http://localhost:3000/friends/2 | jq '.data.relationships.loans.links.self'


"http://localhost:3000/friends/2/relationships/loans"
Working with relationships 37

The specification has a section about updating to-many relationships, it says that:

A server MUST respond to PATCH, POST, and DELETE requests to a URL from a to-
many relationship link as described below.

What this means is that we should be able to make any of the above requests to the URL
http://localhost:3000/friends/2/relationships/loans and they should work. Lets explore
next how every request behaves.

POST requests
A request of type POST is used when we want to add new members to a relationship. Since this is a
has-many relationship, the type of the data we pass is an array with resource linkages. If any of the
objects are already present, they should not be added again.
Lets try this out by pushing the loan with ID 1 to this relationship:

https://git.io/vwtT4

curl http://localhost:3000/friends/2/relationships/loans -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":[{"id":"3", "type":"friends"}]}'

The request above probably failed, with an error like this:

{
"errors": [
{
"title": "Type Mismatch",
"detail": " is not a valid type for this operation.",
"id": null,
"href": null,
"code": "116",
"source": null,
"links": null,
"status": "400",
"meta": null
}
]
}

http://jsonapi.org/format/1.0/#crud-updating-to-many-relationships
Working with relationships 38

We hit the correct URL and sent the correct body, but there was a mistake on the type because
we specified friends instead of loans, which would have been the correct type. This error was
introduced on purpose, so we could gain some awareness about the type of checks that we need to
have when doing our own server implementations of JSON API.
Lets do the request again, this time sending the correct type:

https://git.io/vwtT0

curl http://localhost:3000/friends/2/relationships/loans -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":[{"id":"1", "type":"loans"}]}' -i

HTTP/1.1 204 No Content


X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Cache-Control: no-cache
X-Request-Id: bf8348eb-3521-4096-b528-e53f05bbf10b
X-Runtime: 0.010521
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Date: Tue, 01 Mar 2016 14:54:35 GMT
Connection: Keep-Alive

Success! We associated our loan article back to its original borrower. Lets verify that by doing a get
request to the friend relationship on the loan:

curl http://localhost:3000/loans/1/relationships/friend | jq
{
"links": {
"self": "http://localhost:3000/loans/1/relationships/friend",
"related": "http://localhost:3000/loans/1/friend"
},
"data": {
"type": "friends",
"id": "2"
}
}
Working with relationships 39

PATCH requests
A request of type PATCH can be used when we want to do a complete replacement of every member
of the relationship. Following the specification, the server has to either replace all the members or
return an error or 403 response if complete replacement is not allowed by the server.
By default JR doesnt allow complete replacement on has-many relationships. We can test that by
doing the following request:

https://git.io/vwtTr

curl http://localhost:3000/friends/2/relationships/loans -X PATCH \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":[]}' | jq

{
"errors": [
{
"code": 403,
"detail": "Complete replacement forbidden for this relationship",
"href": null,
"id": null,
"links": null,
"source": null,
"status": "403",
"title": "Complete replacement forbidden"
}
]
}

To enable complete replacement, we need to go to the friends resource and add acts_as_set: true
to the has_many relationship.

app/resources/friend_resource.rb

class FriendResource < JSONAPI::Resource


attribute :first_name
attributes :last_name, :email, :twitter

has_many :loans, acts_as_set: true


end

Once we have enabled complete replacement, we can repeat the request:


Working with relationships 40

https://git.io/vwtT1

curl http://localhost:3000/friends/2/relationships/loans -X PATCH \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":[]}' -i

HTTP/1.1 204 No Content


X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Cache-Control: no-cache
X-Request-Id: 52d7f5de-8320-4170-83d1-6ade85270f10
X-Runtime: 0.007267
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Date: Tue, 01 Mar 2016 15:10:03 GMT
Connection: Keep-Alive

Lets create a new loan without specifying a friend and well use a PATCH to associate all of the
existing loans to the friend with ID 2.
We need to create a new article and the loan for that article.
The theme for our friends and articles are motorcycles and Dakar Rally winners, so lets add our
shiny new Honda 450R to the database:

https://git.io/vwtT5

curl http://localhost:3000/articles -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type":"articles","attributes":{"name": "Honda CRF450R\
","available":"true"}}}'

Now well get it ready for lending:

https://git.io/vwtkJ

curl http://localhost:3000/loans -X POST \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":{"type": "loans","attributes": {"notes": "Minor issue\
with the throttle","returned": false},"relationships":{"article": {"data":{"typ\
e": "articles", "id": "2"}}}}}'

{
"data": {
Working with relationships 41

"id": "2",
"type": "loans",
"links": {
"self": "http://localhost:3000/loans/2"
},
"attributes": {
"notes": "Minor issue with the throttle",
"returned": false
},
"relationships": {
"article": {
"links": {
"self": "http://localhost:3000/loans/2/relationships/article",
"related": "http://localhost:3000/loans/2/article"
}
},
"friend": {
"links": {
"self": "http://localhost:3000/loans/2/relationships/friend",
"related": "http://localhost:3000/loans/2/friend"
}
}
}
}
}

Now that we two loans in our database, lets associate both at the same time to a friend, by
doing a PATCH request:

curl http://localhost:3000/friends/2/relationships/loans -X PATCH \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":[{"id":"1", "type":"loans"},{"id":"2", "type":"loans"\
}]}' -i

HTTP/1.1 204 No Content


X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Cache-Control: no-cache
X-Request-Id: 78fcdd1a-4e51-48f7-9b0e-512c694c079f
X-Runtime: 0.055333
Working with relationships 42

Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)


Date: Tue, 01 Mar 2016 15:24:43 GMT
Connection: Keep-Alive

With that, we have associated both loans to our friend. Although the request data looks very similar
to the one we sent in the POST, we need to remember that POST requests will add the objects to the
relationship, while PATCH replaces the entire content. We can experiment a little bit more with this
once we have talked about DELETE.

DELETE request
We can use DELETE requests to delete one or multiple members from a relationship. If all the members
can be removed, or if they are missing, the server response should be successful. The last bit is used
to avoid race conditions if we have two requests trying to delete the same resource, for example.
Lets use this operation to remove the motorcycle loan:

https://git.io/vwtkq

curl http://localhost:3000/friends/2/relationships/loans -X PATCH \


-H 'Content-Type: application/vnd.api+json' \
--data-binary '{"data":[{"id":"2", "type":"loans"}]}' -i

HTTP/1.1 204 No Content


X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Cache-Control: no-cache
X-Request-Id: 2061b567-8246-4867-9f8b-969641194443
X-Runtime: 0.011450
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Date: Tue, 01 Mar 2016 15:35:50 GMT
Connection: Keep-Alive

Wrapping up
With the last DELETE example, we have covered all possible ways that we have available to update
relationships both to one or to many. Readers are encouraged to experiment further with both PATCH
and POST; the following is a list of requests that can help us better understand how the specification
works:

Send a PATCH that contains only the loan with id 2


Working with relationships 43

POST with both loans


Update the relationship through the articles links
Delete loans with random identifiers

In the next and final chapter, well explore the section about fetching data.
http://jsonapi.org/format/#fetching
Fetching data
In this chapter, well talk about different ways to fetch data from the server. The relevant section of
the JSON API spec is fetching data.

Side-loading related resources


When we covered top-level members, we mentioned that there could be a property called included
that will contain an array of resource objects related to the primary data; when there is included
data, we can avoid extra requests to the API while trying to fetch resources linked in a relationship.
Lets suppose we are working on mobile application and we want to render the friend and article
data in the loan page using the data that comes by default with the resource loan. For this example,
lets simulate the possible list of requests after visiting the page for the loan with identifier 1.
First, our client will make a request to the resource endpoint, which will return the following data:

{
"data": {
"id": "1",
"type": "loans",
"links": {
"self": "http://localhost:3000/loans/1"
},
"attributes": {
"notes": "minor scratches in the face shield",
"returned": false
},
"relationships": {
"article": {
"links": {
"self": "http://localhost:3000/loans/1/relationships/article",
"related": "http://localhost:3000/loans/1/article"
}
},
"friend": {

http://jsonapi.org/format/1.0/#fetching

44
Fetching data 45

"links": {
"self": "http://localhost:3000/loans/1/relationships/friend",
"related": "http://localhost:3000/loans/1/friend"
}
}
}
}
}

With this, we can fill the placeholder data for notes associated with the loan, but we still dont
have data to display the friend detail or the article. To do so well need to make two extra requests,
following the related links for each of the relationship items, ending in three total requests to
display all of the data related with a loan.
Now lets explore the same scenario, this time using the ability offered by JSON API of side-loading
data.
If the payload doesnt include a related resource, JSON API allow us to ask for this related resource
by using the include request parameter. This parameter must be a comma-separated list ,with the
name of the relationship or relationship paths.
Going back to our example, if we want to include the friend and article, the URL would like this:
http://localhost:3000/loans/1?include=friend,article.

We can try it out by running curl to the URL above.

curl http://localhost:3000/loans/1?include=friend,article | jq

{
"data": {
"id": "1",
"type": "loans",
"links": {
"self": "http://localhost:3000/loans/1"
},
"attributes": {
"notes": "minor scratches in the face shield",
"returned": false
},
"relationships": {
"article": {
"links": {
"self": "http://localhost:3000/loans/1/relationships/article",
Fetching data 46

"related": "http://localhost:3000/loans/1/article"
},
"data": {
"type": "articles",
"id": "1"
}
},
"friend": {
"links": {
"self": "http://localhost:3000/loans/1/relationships/friend",
"related": "http://localhost:3000/loans/1/friend"
},
"data": null
}
}
},
"included": [
{
"id": "1",
"type": "articles",
"links": {
"self": "http://localhost:3000/articles/1"
},
"attributes": {
"name": "AGV helmet",
"available": true
},
"relationships": {
"loans": {
"links": {
"self": "http://localhost:3000/articles/1/relationships/loans",
"related": "http://localhost:3000/articles/1/loans"
}
}
}
}
]
}

Now we can see the related resources in the included property. Its worth looking at the
relationships object it now includes not only links but also the resource linkage in the data
property. This particular resource has an article, but no friend. This state is represented in the
Fetching data 47

relationships. Having data: null is valid and it means that this relationship doesnt have a resource
associated with it yet.
The include request parameter can also be used to request resources related to other resources. For
example, if we had another resource called category, and a relationship between articles and the cat-
egory, we could have asked for the category to be side-loaded by adding a dot to the relationship like
this: article.category. Used in a full URL it would be: http://localhost:3000/loans/1?include=article.categ
Servers dont have to support the include parameter. For scenarios where they dont, or when a
relationship is not identified, the server should return a 400 bad request response.
More information about side-loading relationships can be found in the section fetching includes.
Next, lets explore how can we ask the API to only include a specific subset of fields for a resource.

Sparse Fieldsets
There may be scenarios where we dont want to bring in all of the attributes related to a resource
lets suppose we only want to show the users email if some criteria is met in the client.
JSON API allow us to fetch only some attributes by using the request parameter fields. Both
attributes and relationships are known in JSON API as fields. As such, there is a restriction where
an attribute and relationship cant have the same name.
The request parameter fields takes the form fields[MODEL-NAME]=field-one,field-two,etc, so
in our friend example, we could fetch only the first and last name like this: fields[friends]=first-
name,last-name.

Lets try to attach the request parameter to a curl command:

curl http://localhost:3000/friends/2?fields[friends]=first-name,last-name | jq

It will fail, because the request parameters have invalid characters, [ and ]. To make the above
request work, well need to percent-encode the parameters following rfc3986.
The percent-encoded version of the request parameter that we used above is the following:
fields%5Bfriends%5D=first-name. Now we can try the request again:

http://jsonapi.org/format/#fetching-includes
http://jsonapi.org/format/#fetching-sparse-fieldsets
http://tools.ietf.org/html/rfc3986#section-3.4
Fetching data 48

curl http://localhost:3000/friends/2?fields%5Bfriends%5D=first-name | jq

{
"data": {
"id": "2",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/2"
},
"attributes": {
"first-name": "Cyril"
}
}
}

It is possible that not everyone knows how to encode parameters following rfc3986 out of the box.
Fortunately for us, curl has an option --data-urlencode that will encode the parameters correctly.

https://git.io/vwtIk

curl -X GET http://localhost:3000/friends/2\


--data-urlencode 'fields[friends]=first-name,last-name' | jq

{
"data": {
"id": "2",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/2"
},
"attributes": {
"first-name": "Cyril"
}
}
}

The option fields is not restricted to the main resource; we can also use it to ask for fields on related
resources. In the following request, we ask for the same friend as above. We also side-load all the
loans and articles associated with it, limiting the fields to id and notes for the loan and id for the
article.
Fetching data 49

https://git.io/vwtIL
curl -X GET http://localhost:3000/friends/2?include=loans.article\
--data-urlencode 'fields[friends]=first-name,last-name' \
--data-urlencode 'fields[loans]=id,notes' \
--data-urlencode 'fields[articles]=id' | jq

Next, lets talk about sorting.

Sorting
JSON API doesnt require servers to implement sorting. If we want to support sorting, there is a
section for it in the specification.
To support sorting, we can use the sort query parameter; it takes as values the name of the fields we
want to use for sorting. We can pass more than one field, separated by a comma; the sorting should
then happen in that order.
Lets use friends as an example, and get them sorted by first name:

https://git.io/vwtIt
curl http://localhost:3000/friends?sort=first-name\
| jq '.data | map(.attributes)'
[
{
"first-name": "Cyril",
"last-name": "Neveu",
"email": "cyryl@neveu.com",
"twitter": null
},
{
"first-name": "Jutta",
"last-name": "Kleinschmidt",
"email": "jutta@dakar.com",
"twitter": null
}
]

By default, the sort order must be ascending. If we want the result to be descending, we can do so
by appending a minus (-) to the field. The following request asks for friends, sorted by first name
in descending order.
http://jsonapi.org/format/1.0/#fetching-sorting
Fetching data 50

https://git.io/vwtIY
curl http://localhost:3000/friends?sort=-first-name\
| jq '.data | map(.attributes)'
[
{
"first-name": "Jutta",
"last-name": "Kleinschmidt",
"email": "jutta@dakar.com",
"twitter": null
},
{
"first-name": "Cyril",
"last-name": "Neveu",
"email": "cyryl@neveu.com",
"twitter": null
}
]

Lets try adding more than one field:

https://git.io/vwtI3
curl http://localhost:3000/friends?sort=-id,first-name\
| jq '.data | map(.attributes)'
[
{
"first-name": "Jutta",
"last-name": "Kleinschmidt",
"email": "jutta@dakar.com",
"twitter": null
},
{
"first-name": "Cyril",
"last-name": "Neveu",
"email": "cyryl@neveu.com",
"twitter": null
}
]

The specification also allows us to sort based on a relationship attribute by using the name of the
relationship, followed by a dot and then the name of the field. For example: if we want to sort the
loans by the friends first names, well use something like friend.first-name.
Fetching data 51

By default, JR makes all attributes sortable. This can be configured by overriding the self.sortable_-
fields method. The following limits sorting to only first and last name:

class FriendResource < JSONAPI::Resource


attribute :first_name
attributes :last_name, :email, :twitter

has_many :loans, acts_as_set: true

def self.sortable_fields(context)
super(context) - [:email, :twitter, :id]
end
end

Now that we have talked about sorting, lets talk about how to filter data.

Filtering
Unlike sorting and sparse fields, JSON API doesnt have a strict specification on how filtering
should work. The keyword filter is reserved for this kind of operation and the server API designer
can implement any strategy, based on their own needs.
The specification contains a recommendation for filtering resources based on relationships. It
suggests that we should be able to combine filter with the association name, passing as argument
a comma-separated list with the IDs of the other part of the relationship.
JR has a built-in strategy for filtering. We can define filters in the resource by using the method
filter or filters. Lets modify our friends resource to add filter options in ID, email, first and last
name.

http://jsonapi.org/format/1.0/#fetching-filtering
http://jsonapi.org/recommendations/#filtering
https://github.com/cerebris/jsonapi-resources#filters
Fetching data 52

app/resources/friend_resource.rb

class FriendResource < JSONAPI::Resource


attribute :first_name
attributes :last_name, :email, :twitter

has_many :loans, acts_as_set: true

filter :id
filters :last_name, :first_name, :email
end

Once we have defined a filter, we can test it. Lets do a request with filtering by ID:

https://git.io/vwtIZ

curl -X GET http://localhost:3000/friends\


--data-urlencode 'filter[id]=2,3' | jq '.data | map(.id)'

If we look at the server logs, well see that the following SQL query was generated: SELECT
"friends".* FROM "friends" WHERE "friends"."id" IN (2, 3) ORDER BY "friends"."id" ASC.

Next, lets try filtering by name

https://git.io/vwtIl

curl -X GET http://localhost:3000/friends\


--data-urlencode 'filter[first-name]=Cyril'\
| jq '.data | map (.attributes."first-name")'

The SQL query for the filter above is: SELECT "friends".* FROM "friends" WHERE "friends"."first_-
name" = 'Cyril' ORDER BY "friends"."id" ASC. The generated filters look for values that are
exact matches.
If we want to extend the filter to be more flexible, we can do so by passing a callback in the apply
option. The following modifies the first name filter to return all records that match a certain pattern.
Fetching data 53

app/resources/friend_resource.rb

class FriendResource < JSONAPI::Resource


attribute :first_name
attributes :last_name, :email, :twitter

has_many :loans, acts_as_set: true

filter :id
filters :last_name, :email
filter :first_name, apply: ->(records, value, _options) {
records.where('friends.first_name LIKE ?', "%#{value.first}%")
}
end

More information about the filter options can be found in the JR README section on about filters.
Next, lets talk about pagination.

Pagination
There are scenarios where we want to limit the number of resources returned by the server. This
technique is called pagination, and the specification includes some recommendations on how we
should handle it.
First, if our server has a pagination strategy, it must then include the links that help us move to the
first, last, next or previous. Those links are included in top-level links object.
Well soon explore how those links for pagination should be built, but before that, lets check the
following payload, which was returned by a server with pagination. Well omit the resources in data
since we are interested in showing how the top-level links object looks when pagination keys have
been included.

https://github.com/cerebris/jsonapi-resources#filters
http://jsonapi.org/format/1.0/#fetching-pagination
Fetching data 54

{
"data": [
...
],
"links": {
"first": "a-valid-link-to-the-first-page-of-data",
"previous": "a-valid-link-to-the-previous-page-of-data",
"next": "a-valid-link-to-the-next-page-of-data",
"last": "a-valid-link-to-last-page-of-data"
}
}

For the scenarios where a link is not available, the key can be null or omitted; suppose we are fetching
the first page, which doesnt have a pagination previous link associated with it, the following
payloads are valid:

Setting the key as null

{
"data": [
...
],
"links": {
"first": "a-valid-link-to-the-first-page-of-data",
"previous": null
"next": "a-valid-link-to-the-next-page-of-data",
"last": "a-valid-link-to-last-page-of-data"
}
}

Or omit the key previous:


Fetching data 55

Omitting the key previous since is not available

{
"data": [
...
],
"links": {
"first": "a-valid-link-to-the-first-page-of-data",
"next": "a-valid-link-to-the-next-page-of-data",
"last": "a-valid-link-to-last-page-of-data"
}
}

Now, lets talk how the pagination links should look. JSON API is agnostic about the pagination
strategy used by the server. The specification mentions some of the well known strategies, such
as page-based, offset-based, and cursor-based. No matter which strategy we implement, JSON API
reserves the query parameters page for pagination purposes, and the page attribute will change
depending on the chosen strategy.
Lets put together all the previous pieces in our example application. JR has built-it support for
pagination and it supports two strategies: paged-based and offset-based.
We can configure a default paginator using JSONAPI.configure. Lets create an initializer file under
config/initializers/jsonapi_resources.rb and set the paged-based paginator as default:

JSONAPI.configure do |config|
# built in paginators are :none, :offset, :paged
config.default_paginator = :paged

config.default_page_size = 10
config.maximum_page_size = 20
end

When using the page-based paginator, the valid parameters for page are page are number and size;
if we want to request the first page and limit the number of resources to just one, the URL will look
something like this: http://localhost:3000/friends?page[number]=1&page[size]=1.
Lets try it out using curl. Remember that [ and ] are not valid characters for a URL, so we need to
either encode them or use --data-urlencode.

https://github.com/cerebris/jsonapi-resources#pagination
Fetching data 56

https://git.io/vwtI0

curl -X GET http://localhost:3000/friends\


--data-urlencode 'page[page]=1'\
--data-urlencode 'page[limit]=1' | jq

{
"data": [
{
"id": "2",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/2"
},
"attributes": {
"first-name": "Cyril",
"last-name": "Neveu",
"email": "cyryl@neveu.com",
"twitter": null
},
"relationships": {
"loans": {
"links": {
"self": "http://localhost:3000/friends/2/relationships/loans",
"related": "http://localhost:3000/friends/2/loans"
}
}
}
}
],
"links": {
"first": "http://localhost:3000/friends?page%5Bnumber%5D=1&page%5Bsize%5D=1",
"next": "http://localhost:3000/friends?page%5Bnumber%5D=2&page%5Bsize%5D=1",
"last": "http://localhost:3000/friends?page%5Bnumber%5D=2&page%5Bsize%5D=1"
}
}

The result now includes the pagination links with the keys first, next and last, all encoded according
to RFC 3986.
If we decode the URLs, they will look like this:
http://tools.ietf.org/html/rfc3986#section-3.4
Fetching data 57

"links": {
"first": "http://localhost:3000/friends?page[number]=1&page[size]=1",
"next": "http://localhost:3000/friends?page[number]=2&page[size]=1",
"last": "http://localhost:3000/friends?page[number]=2&page[size]=1"
}

JR also allows us to override the paginator method by resource, so lets change the pagination
method for the friends resource to use the offset-based strategy. To do so, lets modify app/re-
sources/friend_resource.rb and paginator :offset:

class FriendResource < JSONAPI::Resource


attribute :first_name
attributes :last_name, :email, :twitter

has_many :loans, acts_as_set: true

filter :id
filters :last_name, :email
filter :first_name, apply: ->(records, value, _options) {
records.where('friends.first_name LIKE ?', "%#{value.first}%")
}

paginator :offset
end

Now if we do a request to the friends URL, well notice that parameters for the page query parameter
have changed to limit and offset.

curl -X GET http://localhost:3000/friends | jq '.links'


{
"first": "http://localhost:3000/friends?page%5Blimit%5D=10&page%5Boffset%5D=0",
"last": "http://localhost:3000/friends?page%5Blimit%5D=10&page%5Boffset%5D=0"
}

This will look like the following when decoded:


Fetching data 58

curl -X GET http://localhost:3000/friends | jq '.links'


{
"first": "http://localhost:3000/friends?page[limit]=10&page[offset]=0",
"last": "http://localhost:3000/friends?page[limit]=10&page[offset]=0"
}

If we are using JR to build our API, its default pagination strategies should be enough. But if we do
want to customize it to our own needs, JR will allow us to do so by creating a custom paginator. The
JR README contains information on how to do it: custom paginators.
We can use pagination for the primary data, and we can also use it to paginate included collections.
If we want to add pagination information in included collections, this information should go into
the links object of the relationship object. JR doesnt support this yet, but if it did, it would probably
look like this:

curl -X GET http://localhost:3000/friends | jq


{
"data": [
{
"id": "2",
"type": "friends",
"links": {
"self": "http://localhost:3000/friends/2"
},
"attributes": {
"first-name": "Cyril",
"last-name": "Neveu",
"email": "cyryl@neveu.com",
"twitter": null
},
"relationships": {
"loans": {
"links": {
"self": "http://localhost:3000/friends/2/relationships/loans?page%5B\
limit%5D=1&page%5Boffset%5D=0",
"related": "http://localhost:3000/friends/2/loans",
"first": "http://localhost:3000/friends/2/relationships/loans?page%5\
Blimit%5D=1&page%5Boffset%5D=0",

https://github.com/cerebris/jsonapi-resources#custom-paginators
Fetching data 59

"next": "http://localhost:3000/friends/2/relationships/loans?page%5B\
limit%5D=1&page%5Boffset%5D=1",
"last": "http://localhost:3000/friends/2/relationships/loans?page%5B\
limit%5D=1&page%5Boffset%5D=1"
}
}
}
}
],
"links": {
"first": "http://localhost:3000/friends?page%5Blimit%5D=1&page%5Boffset%5D=0\
",
"next": "http://localhost:3000/friends?page%5Blimit%5D=1&page%5Boffset%5D=1",
"last": "http://localhost:3000/friends?page%5Blimit%5D=1&page%5Boffset%5D=1"
}
}

We have now covered the every section in the specification and learned how to build and fetch data
using JSON API! Lets wrap up with the next section, which suggests where me might go from here.

Whats next?
JSON API is still young; well probably see some changes in the specification as more projects begin
to adopt it. In this book, we covered version 1.0 of the specification. Version 1.1 is currently under
development.
The main goal of this book is to introduce you to the specification with a hands-on approach, using
JSONAPI::Resources a library built in Ruby and meant to be used with Ruby on Rails. That
doesnt mean that this is the only library out there, though; the website contains a section which
links to a number of different implementations:
http://jsonapi.org/implementations/.
If you need more help, there is an IRC channel on Freenode #jsonapi or a discussion forum
http://discuss.jsonapi.org/.
Ill like to encourage people to go the the website and poke around, there are a couple of section that
we didnt cover here, like the one about extensions [http://jsonapi.org/extensions].
http://jsonapi.org/format/1.0/
http://jsonapi.org/format/1.1/
http://jsonapi.org/implementations/
http://discuss.jsonapi.org/
Fetching data 60

Ill try to keep the book up to date, to reflect major changes in the specification. I hope you enjoyed it,
and remember that you can always reach me at: builes.adolfo@gmail.com, or follow me on Twitter
for updates @abuiles.

https://twitter.com/abuiles