Skip to content

Instantly share code, notes, and snippets.

@ahx
Created August 31, 2018 09:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ahx/50a671cad39dfbec6fc8ebf6ac6ffc9b to your computer and use it in GitHub Desktop.
Save ahx/50a671cad39dfbec6fc8ebf6ac6ffc9b to your computer and use it in GitHub Desktop.

JSON:API

Your anti bikeshedding weapon.

By following shared conventions, you can increase productivity, take advantage of generalized tooling, and focus on what matters: your application.

jsonapi.org


{
  "links": {
    "self": "http://example.com/articles",
    "next": "http://example.com/articles?page[offset]=2",
    "last": "http://example.com/articles?page[offset]=10"
  },
  "data": [{
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON API paints my bikeshed!"
    },
    "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" }
        ]
      }
    },
    "links": {
      "self": "http://example.com/articles/1"
    }
  }],
  "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"
    }
  }]
}

  1. Relationships
  2. Links
  3. Sideloading
  4. Conventions, continued

HATEOAS

Hypermedia As The Engine Of Application State (HATEOAS) is a component of the REST application architecture that distinguishes it from other network application architectures.

With HATEOAS, a client interacts with a network application whose application servers provide information dynamically through hypermedia. A REST client needs little to no prior knowledge about how to interact with an application or server beyond a generic understanding of hypermedia. – https://en.wikipedia.org/wiki/HATEOAS


{
  data: {
    id: '100'
    name: 'Ada',
    tenant_id: '2', // "has one tenant"
    planning_unit_ids: ['1', '2'], // "has many planning_units"
  }
}

Relationships


Links to related resources

JSON:API links objects

GET /users/100?include=tenant,planning_units

GET /articles/1?include=comments,comments.author

{ 
  data: {
    id: '100'
    type: 'users',
    attributes: {
      name: 'Ada'
    },
    relationships: {
      // "has one tenant"
      tenant: {
        links: {
          related: https://example.com/tenants/2
        }
      },
      // "has many planning_units"
      planning_units: {
        links: {
          related: https://example.com/users/planning_units/1,2
        }
    }
  }
}

Need / know foreign keys?

JSON:API resource linkage.

{
  data: {
    id: '100',
    type: 'users',
    relationships: {
      // "has one tenant"
      tenant: {
        links: {
          related: 'https://example.com/tenants/2'
        }
        data: {
          type: 'tenant',
          id: '2'
        }
      },
      // "has many planning_units"
      planning_units: {
        links: {
          related: 'https://example.com/users/planning_units/1,2'
        }
        data: [
          {
            type: 'planning_unit',
            id: '1'
          },
          {
            type: 'planning_unit',
            id: '2'
          }
        ]
      }
    }
  }
}

Example: Updating the author along with the resource

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" }
      }
    }
  }
}

Example: Updating the relation by itself

PATCH /articles/1/relationships/author HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": { "type": "people", "id": "12" }
}

Resource level links

{
  "data": {
    "type": "invoice",
    "id": "093b941d",
    "links": {
      "pay": "https://example.com/invoices/093b941d/payment_attempts"
    }
    "attributes": {
      "created_at": "2017-06-15 12:31:01Z",
      "sent_at": "2017-06-15 12:34:29Z",
      "paid_at": "2017-06-16 09:05:00Z",
      "status": "published"
    }
  },
}

Sideloading

Inclusion of Related Resources

GET /articles/1?include=author
{
  data: {
    id: '1',
    type: 'article',
    attributes: {
      title: 'An Article'
    },
    relationships: {
      author: {
        data: {
          type: 'author',
          id: '2'
        }
      }
    }
  },
  included: [
    {
      id: '1',
      type: 'author',
      attributes: {
        name: 'Bob'
      },
    }
  ]
}

Conventions, continued

Filtering

GET https://example.com/articles?filter[author]=1

Sparse fieldsets

GET https://example.com/articles?fields[articles]=title,created_at,author

Pagination

What questions do you have?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment