json-api-0.1.2.0: README.md
[](https://circleci.com/gh/toddmohney/json-api)
## Haskell Implementation of the JSON-API specification
#### Motivation
From the specification itself:
> If you’ve ever argued with your team about the way your JSON responses should
> be formatted, JSON API can be your anti-bikeshedding tool.
>
> By following shared conventions, you can increase productivity, take advantage
> of generalized tooling, and focus on what matters: your application.
>
> Clients built around JSON API are able to take advantage of its features around
> efficiently caching responses, sometimes eliminating network requests entirely.
All in all, API discoverability and other [HATEOAS](https://spring.io/understanding/HATEOAS)
principles make JSON-API an attractive resource serialization option.
#### The specification
Find the specification [here](http://jsonapi.org/)
#### Example usage
Let's start with an example User record:
```Haskell
data User = User
{ userId :: Int
, userFirstName :: String
, userLastName :: String
} deriving (Eq, Show)
$(deriveJSON defaultOptions ''User)
```
From this, we can use the `json-api` package to produce a payload conformant
to the [JSON-API specification](http://jsonapi.org/) like so:
```Haskell
-- Builds the Document which will be serialized as our
-- web server's response payload
mkDocument :: User -> Links -> Document User Text Int
mkDocument usr links =
Document
(Singleton $ toResource usr)
(Just links)
Nothing
-- Helper function to convert a User into a resource object
-- This could be our canonical serialization function for a User in any
-- response payload
toResource :: User -> Resource User Text
toResource user =
Resource resourceId resourceType user resourceLinks resourceMetaData
where
resourceId = ResourceId . pack . show . userId $ user
resourceType = ResourceType "User"
resourceLinks = Just $ userLinks user
resourceMetaData = Nothing
-- helper function to build links for a User resource
userLinks :: User -> Links
userLinks user = toLinks [ ("self", selfLink) ]
where
selfLink = toURL selfPath
selfPath = "/users/" <> (show $ userId user)
```
When delivered as a response from a web server, for example, we get a payload
that looks like this:
```JSON
{
"data":{
"attributes":{
"userFirstName":"Isaac",
"userLastName":"Newton",
"userId":1
},
"id":"1",
"meta":null,
"type":"User",
"links":{
"self":"/users/1"
}
},
"meta":null,
"links":{
"self":"/users/1"
}
}
```
Neat! We can see that if we would like the full User data for the User with
ID=1, we can query `/users/1`. Discoverability!
We can also see from the top-level `links` data that this particular payload originated
from `/users/1`.
This is a very simple example to provide an introduction to the basic idea
behind JSON-API and how to use this library. Check out [these examples](http://jsonapi.org/examples/)
for more robust representations of resourceful payloads. Here, you'll start to
see the more comprehensive benefits of a discoverable API.
##### Pagination Example
Let's use the same example User record:
```Haskell
data User = User
{ userId :: Int
, userFirstName :: String
, userLastName :: String
} deriving (Eq, Show)
$(deriveJSON defaultOptions ''User)
```
Suppose we now have a list of 2 users;
```Haskell
let usrs =
[ User 1 "Isaac" "Newton"
, User 2 "Albert" "Einstein"
]
```
From this, we can use the `json-api` package to produce a payload for a collection with pagination links conformant
to the [JSON-API pagination specification](https://jsonapi.org/format/#fetching-pagination) like so:
```Haskell
let paginate = Pagination (PageIndex 1) (PageSize 1) (ResourceCount $ toEnum (length usrs))
let resourceLink = (fromJust . importURL) "/users"
let paginationLinks = mkPaginationLinks PageStrategy resourceLink paginate
let doc = mkDocument [head usrs] (Just paginationLinks) Nothing
```
When delivered as a response from a web server, for example, we get a payload
that looks like this:
```JSON
{
"data": [
{
"attributes": {
"userFirstName": "Isaac",
"userLastName": "Newton",
"userId": 1
},
"relationships": null,
"id": "1",
"meta": null,
"type": "users",
"links": null
}
],
"meta": null,
"included": [
],
"links": {
"next": "/users?page%5bsize%5d=1&page%5bnumber%5d=2",
"first": "/users?page%5bsize%5d=1&page%5bnumber%5d=1",
"last": "/users?page%5bsize%5d=1&page%5bnumber%5d=2"
}
}
```
The key function in the code example is `mkPaginationLinks` which has the following signature;
```Haskell
mkPaginationLinks :: Strategy -> URL -> Pagination -> Links
```
`Strategy` is a sum type that represents the different paging strategies as laid out in the [JSON-API pagination specification](https://jsonapi.org/format/#fetching-pagination). At the time of writing this README, the library only supports 2 paging strategies Offset and Page. Offset is a 0 index based approach unlike Page, i.e. `page[offset]` 0 is the same as `page[number]` 1.
The `URL` type is used to build the links that appear in the JSON payload. The `Pagination` type contains the requisite information for the `mkPaginationLinks` function to generate the paging links.
So let's break this example down. To get started we need to create a `Pagination` record. The first attribute of the record is `PageIndex`. This attribute informs the caller that the page we are looking at is the first in the entire collection (`PageIndex` is either a 0 based index or 1 based index depending on the `Strategy`). So in our example as we are using `PageStrategy`, `PageIndex 1` implies we are after the first page. The second attribute of the record is `PageSize`. This atrribute tells the caller how many items can appear in the list at most. So in our example seeing there are only 2 users, a `PageSize` of 1 would mean that in total we have 2 pages. The third attribute is `ResourceCount`. This attribute is required by the function `mkPaginationLinks` to figure out which links to generate.
The links object in the JSON payload can have 4 attributes `next`, `prev`, `first` and `last`. This library only generates valid links. For example if the request is for the first page of a list, then the `prev` link is not present.
#### Example Project
There is an [example project](https://github.com/toddmohney/json-api/tree/master/example) illustrating how the library can be used in the context of a web server.
#### Hackage
Module documentation can be found on [Hackage](http://hackage.haskell.org/package/json-api)