Skip to content

Latest commit

 

History

History
453 lines (388 loc) · 11.4 KB

README.md

File metadata and controls

453 lines (388 loc) · 11.4 KB

JSON:API

Hatchify implements JSON:API 1.1. The JSON:API specification leaves some decisions up to the implementor, so this document reviews Hatchify's implementation specifically:

The following is an overview of HatchifyJS's JSON:API implementation:

Reading Lists

To get a list of records, you do something like:

GET /api/sales-people

and the response will look like

{
  "jsonapi": {
    "version": "1.0"
  },
  "data": [
    {
      "type": "SalesPerson",
      "id": "ffaf131e-9e27-4bd6-a715-59fff9ed5044",
      "attributes": {
        "name": "Justin",
        "dateHired": "2000-01-01"
      }
    },
    {
      "type": "SalesPerson",
      "id": "e38bff50-61dd-40ce-b29c-725cb9a2735f",
      "attributes": {
        "name": "Roye",
        "dateHired": "2020-01-01"
      }
    }
  ],
  "meta": {
    "unpaginatedCount": 2
  }
}

However thanks to JSON:API the query string is able to filter, paginate, sort, include relationships and sparse fields.

Filtering

While the JSON:API specs suggests having a filter query string parameter, they are agnostic about the strategies supported by the server. HatchifyJS supports "MongoDB" style filtering operators below:

GET /api/sales-people?
  filter[name]=Justin
  filter[name]=%00                      // %00 is an encoded null value
  filter[name][$eq]=Justin
  filter[name][$ne]=Justin
  filter[name][$in]=Justin&filter[name][$in]=Roye
  filter[name][$nin]=Justin&filter[name][$nin]=Roye
  filter[name][$gt]=Roye
  filter[name][$gte]=Arthur
  filter[name][$gte]=Arthur
  filter[name][$lt]=Roye
  filter[name][$lte]=Roye
  filter[name][$ilike]=startsWith%25   // %25 is a % encoded
  filter[name][$ilike]=%25endsWith  
  filter[name][$ilike]=%25contains%25
  filter[name][$like]=startsWithCaseSensitive%25

A response of filtered results will look similar to the one below where irrelevant results will be omitted based on the query string parameters:

{
  "jsonapi": {
    "version": "1.0"
  },
  "data": [
    {
      "type": "SalesPerson",
      "id": "ffaf131e-9e27-4bd6-a715-59fff9ed5044",
      "attributes": {
        "name": "Justin",
        "dateHired": "2000-01-01"
      }
    }
  ],
  "meta": {
    "unpaginatedCount": 1
  }
}

Read more on HatchifyJS's filtering support

Paginating

The JSON:API specs allows a page query string parameter to control pagination but they are agnostic about the strategies supported by the server. HatchifyJS supports a few pagination strategies you can choose from:

GET /api/sales-people?
  page[limit]=5
  page[offset]=10
  page[number]=2
  page[size]=20

A response of paginated results will look similar to the one below and when the unpaginatedCount value is greater than the amount of results it indicates there is more data available than the requested page size:

{
  "jsonapi": {
    "version": "1.0"
  },
  "data": [
    {
      "type": "SalesPerson",
      "id": "ffaf131e-9e27-4bd6-a715-59fff9ed5044",
      "attributes": {
        "name": "Justin",
        "dateHired": "2000-01-01"
      }
    }
  ],
  "meta": {
    "unpaginatedCount": 10
  }
}

Read more on HatchifyJS's paginating support

Relationships

One of the JSON:API builtin advantages is to pull in compacted related references:

GET /api/sales-people?
  include=todos,accounts,accounts.owner

A response of results with related data will look similar to the one below:

{
  "jsonapi": {
    "version": "1.0"
  },
  "data": [
    {
      "type": "SalesPerson",
      "id": "ffaf131e-9e27-4bd6-a715-59fff9ed5044",
      "attributes": {
        "name": "Justin",
        "dateHired": "2000-01-01"
      },
      "relationships": {
        "todos": {
          "data": [
            {
              "type": "Todo",
              "id": "d65c0ab4-b61e-4147-97f3-cba1a0a88e03"
            }
          ]
        },
        "accounts": {
          "data": [
            {
              "type": "Account",
              "id": "77667b72-534c-4c22-9b01-7b49158d6015"
            }
          ]
        }
      }
    }
  ],
  "included": [
    {
      "type": "Todo",
      "id": "d65c0ab4-b61e-4147-97f3-cba1a0a88e03",
      "attributes": {
        "name": "Cleanup"
      }
    },
    {
      "type": "Account",
      "id": "77667b72-534c-4c22-9b01-7b49158d6015",
      "attributes": {
        "name": "Acme"
      },
      "relationships": {
        "owner": {
          "data": {
            "type": "User",
            "id": "77667b72-534c-4c22-9b01-7b49158d6015"
          }
        }
      }
    },
    {
      "type": "User",
      "id": "77667b72-534c-4c22-9b01-7b49158d6015",
      "attributes": {
        "name": "Justin"
      }
    }
  ],
  "meta": {
    "unpaginatedCount": 1
  }
}

Read more on HatchifyJS's relationship support

Sorting

The JSON:API specs have detailed guidelines how to implement the sort query string parameter. HatchifyJS implements these with added support for sorting relationships as well:

GET /api/sales-people?
  include=salesGroup
  sort=-importance,salesGroup.name

Read more on HatchifyJS's sorting support

Sparse Fields

The JSON:API specs have detailed guidelines how to implement the fields query string parameter. HatchifyJS implements it with a slight difference in casing of the schemas:

GET /api/sales-people?
  include=todos
  &fields[SalesPerson]=name
  &fields[Todo]=name,importance

A response with sparse fields will look similar to we we had above but only specified fields will be returned:

{
  "jsonapi": {
    "version": "1.0"
  },
  "data": [
    {
      "type": "SalesPerson",
      "id": "ffaf131e-9e27-4bd6-a715-59fff9ed5044",
      "attributes": {
        "name": "Justin"
      },
      "relationships": {
        "todos": {
          "data": [
            {
              "type": "Todo",
              "id": "d65c0ab4-b61e-4147-97f3-cba1a0a88e03"
            }
          ]
        }
      }
    }
  ],
  "included": [
    {
      "type": "Todo",
      "id": "d65c0ab4-b61e-4147-97f3-cba1a0a88e03",
      "attributes": {
        "name": "Cleanup",
        "importance": 7
      }
    }
  ],
  "meta": {
    "unpaginatedCount": 1
  }
}

Read more on HatchifyJS's sparse fields support

Reading a single record

When you know the ID of the record you are looking for, you can fetch it like:

GET /api/sales-people/ffaf131e-9e27-4bd6-a715-59fff9ed5044

And get a successful response that looks like:

{
  "jsonapi": {
    "version": "1.0"
  },
  "data": {
    "type": "SalesPerson",
    "id": "ffaf131e-9e27-4bd6-a715-59fff9ed5044",
    "attributes": {
      "name": "Justin",
      "dateHired": "2000-01-01"
    }
  }
}

Relationships and sparse fields from above are also supported when reading a single record.

Creating a record

The JSON:API specs allows creation of a new resource and attaching it to pre-existing IDs in the same request:

POST /api/sales-people
Content-Type: application/vnd.api+json
{
  "jsonapi": {
    "version": "1.0"
  },
  "data": {
    "type": "SalesPerson",
    "attributes": {
      "name": "Justin",
      "dateHired": "2000-01-01"
    },
    "relationships": {
      "todos": {
        "data": [
          {
            "type": "Todo",
            "id": "d65c0ab4-b61e-4147-97f3-cba1a0a88e03"
          }
        ]
      }
    }
  }
}

Successful response includes an ID and no relationships even though we are linking them:

{
  "jsonapi": {
    "version": "1.0"
  },
  "data": {
    "type": "SalesPerson",
    "id": "ffaf131e-9e27-4bd6-a715-59fff9ed5044",
    "attributes": {
      "name": "Justin",
      "dateHired": "2000-01-01"
    }
  }
}

Read more on HatchifyJS's creating

Updating a record

The JSON:API specs allows update of an existing resource and attaching it to pre-existing IDs in the same request:

PATCH /api/sales-people/ffaf131e-9e27-4bd6-a715-59fff9ed5044
Content-Type: application/vnd.api+json
{
  "jsonapi": {
    "version": "1.0"
  },
  "data": {
    "type": "SalesPerson",
    "id": "ffaf131e-9e27-4bd6-a715-59fff9ed5044",
    "attributes": {
      "dateHired": "2020-01-01"
    },
    "relationships": {
      "todos": {
        "data": [
          {
            "type": "Todo",
            "id": "0f6fc95d-622e-43cc-9a44-0b7ef7793be3"
          }
        ]
      }
    }
  }
}

Successful response includes an ID and no relationships. Behind the scenes, all linked todos will be unlinked from the sales person and the new todo will be linked now. If other relationships exist they will not be touched.

{
  "jsonapi": {
    "version": "1.0"
  },
  "data": {
    "type": "SalesPerson",
    "id": "ffaf131e-9e27-4bd6-a715-59fff9ed5044",
    "attributes": {
      "dateHired": "2020-01-01"
    }
  }
}

Read more on HatchifyJS's updating

Deleting a record

The JSON:API specs allows deletion of an existing resource by its ID:

DELETE /api/sales-people/ffaf131e-9e27-4bd6-a715-59fff9ed5044

Successful response will look like:

{
  "jsonapi": {
    "version": "1.0"
  },
  "data": null
}

Read more on HatchifyJS's deleting

Unsupported features

  • /relationships endpoints. Currently we have to update the record which will overwrite all existing relationships.