RESTful JSON API Design (PART 3)
PART 3. This is the third and last part in a series of articles on designing RESTful JSON APIs, here is the first and second part. This part will be about query strings and authentication.
QUERY STRINGS
Query strings are a set of keys and values present in your url that affects the output or the action of a request, for example: /endpoint?offset=0&limit=10. Here, the query string begins with a question mark (?) and is followed by a key and a value separated by a equal sign (=). Two different key-value pairs are separated by an ampersand (&).
I have already mentioned one type of query string before and that is the action key which I talked about briefly in the first part. I will mention it in this part as well. Since query strings aren’t following the same rules as the resource structure we developed in part 1, it’s important that we stick to a predefined set of keys. The reason for this is so we can instinctively know which keys can be used and know what to expect by using them, this is the whole idea of the resource structure so let’s implement it here as well.
These are the predefined query string keys: fields, expand, offset, page, limit, sort and action. Here is an explanation of each of them.
Fields
This key can be used on either resource type, its value is a comma separated list of keys you wish the output JSON object to contain. Ex:
GET /tracks/123?fields=nr,name -> 200 { track: { href: “/tracks/123”, id: 123, nr: 1, name: “Track Name #1”, } }
Notice that href and id are always present in the output but that no more keys than nr and name, which is specified in the query strings, are returned.
Expand
Expand is used when you want to show more data of a nested object. The value is a comma separated list of keys you want to expand. By default, a nested object should only show href and id if expand is not defined. Ex:
GET /albums/123 -> 200 { album: { href: ‘/albums/123’, id: 123, name: ‘The Marshall Mathers LP’, artist: { href: ‘/artists/12’, id: 12, } } }
With expand, ex:
GET /albums/123?expand=artist -> 200 { album: { href: ‘/albums/123’, id: 123, name: ‘The Marshall Mathers LP’, artist: { href: ‘/artists/12’, id: 12, name: ‘Eminem’, fullName: ‘Marshall Mathers’, } } }
This is also true for collection resources which should not be expanded by default, ex:
GET /albums -> 200 { ... albums: [ { href: ‘/albums/1’, id: 1, }, { href: ‘/albums/2’, id: 2, }, } }
A single resource response shouldn't need to have expand defined. It is implied that you want the single resource to be expanded by default.
Offset/Page
Offset and page are integer values which can only be applied to a collection resource and is used in combination with limit to enable pagination. Offset is the exact index from where the objects should be returned from and page times limit gives the start index of the returned objects. These values have a relationship as such: offset = (page - 1) * limit. You can only use one of them in a request. Ex:
GET /albums?offset=2 -> 200 { … albums: [ { href: ‘/albums/3’, id: 3, }, { href: ‘/albums/4’, id: 4, }, { href: ‘/albums/5’, id: 5, }, ], }
The default values for these should be set to offset=0 and page=1.
Limit
Limit is an integer value and goes together with offset and page to enable pagination and is the number of items the user wish to be returned from a collection resource. Ex:
GET /tracks?limit=1 -> 200 { … tracks: [ { href: ‘/tracks/1’, id: 1, }, ], }
You can choose the default values for limit which best suits your project but a recommended default value is 10. Remember to also define a maximum limit so a user can't retrieve too many objects in too large of a response.
Sort
Sort is a comma separated list of keys that you want the returned list from a request to be sorted by. The order of the list decides the priority of the sort where the first one has the highest priority. You can decide if the sort should be ascending or descending by having a minus sign infront of the key for descending and leaving it out for ascending. Ex:
GET /artists?limit=3&expand=track&sort=name,-age -> 200 { … artists: [ { href: ‘/artists/1’, id: 1, name: ‘Arnold’, age: 43, }, { href: ‘/artists/3’, id: 3, name: ‘Arnold’, age: 35, }, { href: ‘/artists/2’, id: 2, name: ‘Boris’, age: 38, }, ], }
Sort should be set to timestamp created by default or similar value.
Action
Action is the key where you have more leeway in defining your own functionality to a request. The value should be something descriptive of what will happen and the HTTP method should tell if an action or a retrieval will happen, GET=retrieval and POST=action. The value should be a lower case string with dashes instead of spaces. Ex:
POST /albums?action=export-covers -> 202 {}
AUTHENTICATION
There are many ways of doing authentication and some of them follow the RESTful guidelines more than others. As always, my feelings on it is that it should be as easy an intuitive as possible. The authentication workflow consists of two API calls, POST /login and GET /logout. A session token is generated and returned by the login request and passed in the header to other API calls. The logout request destroys the session token and ends the ability to use the token for authentication. Login ex:
POST /login { username: ‘materik’, password: ‘********’, } -> 200 { user: { href: ‘/users/123’, id: 123, username: ‘materik’, } } HEADER { … Set-Cookie: ‘connect.sid=<token>; …’, }
Notice that the user object is returned by the login request and that the session cookie is returned in the header. The key of the header returned by the login is Set-Cookie but the authenticated requests after this should have the token returned in the header with the key: Cookie, ex:
GET /users/123/albums HEADER { Cookie: ‘connect.sid=<token>’, } -> 200 { … albums: [ … ] }
To kill the session you request logout, ex:
GET /logout -> 204
The logout request returns 204 NO CONTENT which says that the request was successful.
THE END
That is all the parts I deemed necessary to know about to create a good and scalable RESTful JSON API. If you have something you want me to add or something else you want to discuss about this series, please contact me on twitter (@thematerik). Thanks for reading and happy coding…
Planning on writing an epilogue that will summarize this series include some more thoughts on API design, stay tuned…














