Designing a Beautiful REST API with JSON (Finalization)
In my previous post I wrote about how to design a beautiful REST API and now is time to finalize the API description with ideas about additional functionality needed in order to achieve the goal for a functional design.
Meta Data
One of the most important elements is to deliver to the client all needed URLs in order to be easy for him when accessing linked resources. But it’s obvious the client can’t use only prepared URLs. Some parameter shall be added to the URLs on client side depending on visualization state and requirements of the view.
For example: a grid with data can be sorted or filtered by user and in this case the client shall be able to extent the given URL with sort and filter information to achieve server side sorting and filtering when needed.
So, at this point we need to define a base URL structure and this way to document for the client the changes allowed for him to do in given URL.
In my case the URL for collection resources shall have following structure:
http[s]://[domain]/api/[version]/[resource]?[parent filter][offset & limit][order][filter][extend][fields]
and the URL for instance resource shall have following structure:
http[s]://[domain]/api/[version]/[resource]/[id]?[extend][fields]
where:
[domain] – the domain path and application folder (if any)
[version] – the version of RESTful API
[resource] – the name of the requested resource
[parent filter] – a set query string parameters, each named properly allowing you to applying filters based on resource linking.
[offset & limit] – query string parameters with paging information for the list.
[order] – client side query string parameter with user defined server side ordering.
[filter] – client side query string parameter with user defined server side filtering.
[extent] – client side query string parameter with list of linked resources to extent into the result.
[fields] – client side query string parameter with list of required fields into the result.
The URL elements marked with blue color plus the pagination parameters are client side definable parameters. They are related only to the way how data is visualized to the user.
When a request with client side parameters is sent to the server for processing, the response shall contain information about those parameters. For this reason I will extend the “href” property into JSON response to an object names “meta”.
Example: { "meta": { "href" : "http[s]://[domain]/api/[version]/[resource]?[parent filter][offset & limit][order][filter][extend][fields]", "hrefTemplate" : "http[s]://[domain]/api/[version]/[resource]?[parent filter]", "order" : [ ... ], "filter" : [ ... ], "expand" : " ... ", "fields" : " ... ", ... } }
A real scenario example of instance response will look like this
GET /emails/345 200 OK { "meta": { "href" : "https://api.foo.com/v1/emails/345", "hrefTemplate" : "https://api.foo.com/v1/emails/345", "expand" : null, "fields" : "email,firstName,LastName,...,lastSource" }, "email" : "x@y.com", ... "lastSource" : { "meta": { "href" : "https://api.foo.com/v1/sources/555", "hrefTemplate" : "https://api.foo.com/v1/sources/555?parent=email/345", "expand" : null, "fields" : "..." } } }
And a real scenario for collection response will look like this:
GET /emails 200 OK { "meta" : { "href" : "https://api.foo.com/v1/emails", "hrefTemplate" : "https://api.foo.com/v1/emails", "order" : [ { "field" : "email", "direction" : "asc"} ], "filter" : [], "expand" : null, "fields" : "email,firstName,lastName,...", "first": { "href": "https://api.foo.com/v1/emails?offset=0" }, "previous": null, "next": { "href": "https://api.foo.com/v1/emails?offset=25" }, "last": { "href": "https://api.foo.com/v1/emails?offset=100" }, "template": { "href": "https://api.foo.com/v1/emails?offset={+offset}&limit={+limit}" }, }, "offset": 0, "limit": 25, "total" : 2500, "items": [ { "meta" : { "href":"https://api.foo.com/v1/emails/1", ... }, "email" : "x1@y.com", ... }, { "meta" : { "href" : "https://api.foo.com/v1/emails/2", ... }, "email" : "x2@y.com", ... } ... ] }
In this design the “hrefTemplate” shall be used from the client to build new URLs when user defines new requirements. And the “href” is exactly what was requested.
As you can see there is some information delivered from the server to the client upon a clear request – like sort order and filed list.
This approach allow your server to “inform” the client about default parameters added to client’s clear request.
For example, requesting an resource collection without parameters can by default:
- limit the request to 25 records
- set offset to zero
- apply default order by some column
- apply default filtering by some visibility column
- apply default expand of some data
- list all fields delivered
This meta-information can be extended with more information like possible expand values, hidden field list or fields meta information.
... "fields" : [ { "name" : "email", "type" : "value", "required" : true }, { "name" : "firstName", "type" : "value", "required" : true }, { "name" : "lastName", "type" : "value", "required" : false }, ... { "name" : "type", "type" : "object", "required" : true, "expandable" : true } ] ...
In this example the “type” parameter is showing what shall be returned to the server in PUT/POST requests. The two possible values are “value” and “object”, but in real word scenario those can be extended to how the filed shall be treated on the client side (“text”,”password”,”select”,”email”, etc.)
Data Transfer
All this is over-sizing the response with information that can be saved on client side and reused in future request by the client (in reasonable time period). This will be possible with applying a request parameter for excluding the meta data.
For example: ?meta=href can force meta content to only href data and suppress all additional information.
Commands
One of the major errors into REST API development is adding commands into REST API. As I mentioned in previous post about this – REST shall only serve resources.
In case you need to have a server side command that can manipulate multiple resources and resource records, there are two possible solutions:
- using command flags into resources
example: having a property “commandName” and sending a POST requestPOST /email/345 { "commandName" : "execute" }
the GET request in this case may return command status like “not started”, “completed”, “XX% processing” or anything else you can find useful.
This solution requires your REST API service to be responsible for running asynchronous business logic processes – and this may not be a good solution.
. - using business logic service separate of REST API – on a separate path
http[s]://[domain]/command/[version]/[resource]/[command]
On a path like the one above you will have the business logic service respond to a POST request to execute a command.
The best scenario is to make this business logic layer to operate though the REST API. This way you may scale you service easily behind a load balance. But most common scenario is when this business layer service works with database directly for to increase the processing speed.
You may have asynchronous commands that can return command token for letter status update request to a path like this:
http[s]://[domain]/command/[version]/[resource]/[command]/[token]
In conclusion I just want to remark this post is also not including anything about security, caching, concurrency control, redirects, maintenance and browser compatibility.