Build a little PAAS with Dokku
I decided to set up a little self-hosted PAAS for the many little projects that come up every day. I decided to use Dokku. Check out my review.
A quick reference to help design JSON-API apis. For those who don't like reading specifications.
Properly organising API endpoints and JSON-serialized data responses it's pretty difficult, especially when you work on a project that will extended and improved by a larger community.
I'm getting used to stick to the JSON-API - version 1.0, considered stable at the time of writing - when defining APIs. Few conventions make your API clearer and many libraries also support serializing and deserializing JSON APIs with ease.
JSON:API is a specification for how a client should request that resources be fetched or modified, and how a server should respond to those requests.
JSON:API is designed to minimize both the number of requests and the amount of data transmitted between clients and servers. This efficiency is achieved without compromising readability, flexibility, or discoverability.
Adopting the full standard is maybe too much for this project.
In short the API must respect few conventions:
Content-Type: application/vnd.api+json
GET /articles HTTP/1.1
Accept: application/vnd.api+json
HTTP/1.1 201 Created
Content-Type: application/vnd.api+json
Code | Verbose | Description |
---|---|---|
200 | OK | resource / relationship fetched correctly |
404 | Not found | resource / relationship not found |
201 | Created | POST request created a resource |
202 | Accepted | POST request accepted but not yet processed |
403 | Forbidden | unsupported request |
409 | Conflict | duplicate operation with the same client id |
Any resource must contain:
and optionally
{
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase"
},
"relationships": {
"author": {
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": { "type": "people", "id": "9" }
}
}
}
Responses contains always a toplevel JSON Object
And at least one of the following:
it may contain also:
Relationship resource objects must at least contain one of:
{
"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"
}
}
The "included" field in the resource description is used to add related resources in order to minimize the number of requests.
NOTE
Included resources must contain a links.self
that is included somewhere else in the response resources.
// excerpt
{ "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"
}
}]
}
/// end excerpt
GET /articles
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "http://example.com/articles"
},
"data": [
{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!"
}
},
{
"type": "articles",
"id": "2",
"attributes": {
"title": "Rails is Omakase"
}
}
]
}
Links are used for pagination, containing resources expressed as urls
The value null
must be used if the specific key is not available
The page
query parameter should be used for pagination. Also ok page[per_page]
etc.
The filter
query parameter should be used. Nothing else is specified.
GET /articles/1
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "http://example.com/articles/1"
},
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!"
},
"relationships": {
"author": {
"links": {
"related": "http://example.com/articles/1/author"
}
}
}
}
}
GET /articles/1/relationships/comments HTTP/1.1
Accept: application/vnd.api+json
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": {
"type": "people",
"id": "12"
}
}
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" }
}
}
}
}
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"
}
}
}
PATCH /articles/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "articles",
"id": "1",
"relationships": {
"author": {
"data": { "type": "people", "id": "1" }
}
}
}
}
One-to-one
PATCH /articles/1/relationships/author HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": { "type": "people", "id": "12" }
}
One-to-many and Many-to-many
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" }]
}
DELETE /articles/1/relationships/comments HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": [
{ "type": "comments", "id": "12" },
{ "type": "comments", "id": "13" }
]
}
Errors must be returned as an Array errors
key on the top-level object.
Each error can include:
Get the latest posts delivered right to your inbox