REST
Created: 2015-12-20 20:18:26 -0800 Modified: 2017-02-02 10:33:14 -0800
12/20/2015 http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
Much of this article talks about making a completely public API, meaning other developers would get access to it.
HTTP verbs
Section titled HTTP verbs- Use GET when there are no side effects for your API.
- GET /tickets - retrieve a list of tickets
- GET /tickets/12 - retrieves a specific ticket
- Use POST when you’re going to create an entirely new object.
- POST /tickets - creates a new ticket
- Use PUT to update multiple fields in an object
- PUT /tickets/12 - updates specific ticket
- Use PATCH when you’re going to modify just part of an object.
- PATCH /tickets/12 - partially update specific ticket
- This can be used, for example, if you wanted an activation/initialization API. It doesn’t map well to CRUD, so you may have an “activated” field in an object that you update via a PATCH.
- PATCH /tickets/12 - partially update specific ticket
- Use DELETE to delete something
- DELETE /tickets/12 - deletes the ticket
It’s important to note that the same URL (“/tickets”) has different meanings based on which HTTP verbs you’re using.
Naming conventions
Section titled Naming conventions- As mentioned above, you use one name to represent almost everything that can be done with that thing, so you don’t do “createTicket” and “getTicket”, you instead just make “tickets” and change your action depending on the HTTP verb.
- Prefer the plural of a word, that way you don’t need to do something weird like “/person/12” to get a specific person, but then “/people” to get all people (note: the article uses the word “person” as it has an irregular plural).
- When talking about APIs with parameters, you typically use a colon before the parameter. For example, with the “/tickets/12” above, this is really “/tickets/:id“.
- When accounting for filtering/sorting/searching (which are side-effect-free functions), you can just supply GET parameters, e.g. “GET /tickets?state=open” (filtering), “GET /tickets?sort=-priority,created_at” (sorting), “GET /tickets?q=return&state=open&sort=-priority,created_at” (searching using both filtering and sorting)
- Make aliases for common queries: “GET /tickets/recently_closed” instead of having to provide some stupidly long filter/search API.
- Prefer snake_case over camelCase.
Other API conventions
Section titled Other API conventions- If, say, consumers may only want certain properties for a list of objects, allow them to pass which properties they’re interested in, that way you save bandwidth and speed up the consumer’s usage of the API. For example, you could change “GET /tickets” into “GET /tickets?fields=id,name&state=open” so that users can get the ID and name of all open tickets.
- When modifying a resource (with PUT, POST, or PATCH), return the representation of the object so that a consumer doesn’t need to query for it. This is useful when the consumer updates, say, the “name” field, but in addition to that change, the “updated_at” timestamp of the object gets changed (and the consumer wants to know it).
- Prefer JSON responses instead of XML responses.
- For requests, JSON should also be used, that way you get types (boolean, null, string, number, array, object), as opposed to URL-encoding something, which gives you strings for everything (“GET /tickets?id” would mean “id” would be a string until parsed). Also, if you do this, there’s this quote from the article:
An API that accepts JSON encoded POST, PUT & PATCH requests should also require theContent-Typeheader be set toapplication/jsonor throw a 415 Unsupported Media Type HTTP status code.
From <http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api>
- For pagination, just read the article; I won’t be including the notes here.
- Let’s say a ticket can have many messages. You may want an API like “/tickets/12/messages” to get/post/whatever messages pertaining to a ticket. Consider whether you need a relational API like that; perhaps a “message” can exist independently from a ticket, and then maybe you would want to be able to get/create them from a “/messages” API.
- Sometimes, you want to get related objects (or just certain fields of them). You could surface an API like “GET /tickets/12?embed=customer.name,assigned_user”. This would return the associated customer’s name (but nothing else about that customer), and all of the assigned_user’s properties (e.g. id, name, etc.). Be careful when implementing this so that you don’t run into the N+1 select issue.
- Rate-limiting: I believe you should perform the rate-limiting on a different layer from your REST server completely, i.e. your load-balancer like nginx, lighttpd or haproxy.
- Caching: again, read the article.
- For HTTP 400 errors (well, 4xx rather, since they are all in the 400s), try to include a JSON representation of the error. This should contain an error code (that the consumer of the API can look up), then a message and a description. If you want, you could put an errors array for something like “validation failed” and then tell them all of the things that failed.
- Make use of HTTP status codes! The article does a good job at covering this (reference).
- Always use it. By using SSL, all communication is encrypted, and the client knows for sure it’s talking to the real REST server (so no MITM attacks). A nice benefit of this is that you don’t need to sign every API request; you can instead use access tokens (e.g. JSON web tokens).
- Do not redirect non-SSL access to SSL APIs. Instead, throw a hard error. You wouldn’t want poorly configured clients to send requests to an unencrypted endpoint.
Documentation
Section titled DocumentationThis only pertains to public APIs obviously, but here are the guidelines:
- Make sure you don’t need to be signed in to read them
- Include examples of complete request/response cycles (try to make sure they’re pastable or CURLable)
Versioning
Section titled VersioningAgain, this matters most with public APIs, although you could benefit even internally by being able to point to a new API. The big question is whether to put a version number in the URL or in the headers. I think more research should be done before coming to a conclusion here.
I wasn’t sure if there was a way to differentiate between these two routes:
- “/users?name=foo” - this would be an admin-only API that would find a user by name
- “/users?token=foo” - this would be a user-available API that would let a user get their own information given their auth token
The reason I was looking was so that I could auth the first route as a “super user” and the second route as a regular user.
The suggestions I got on-stream in early 2016 were:
- Either inject the JWT into the request
- Make middleware to separate the two (this is not advised)
- EveryJuan said that generally, you should make an auth route, get a token from that, then use that on all other routes. He suggests following the oAuth concept of authn and authz - there’s a “/auth” route where you get the token, then you store on the server a flag per token. His job’s software has different roles (customers, restaurants, admins), and there’s a flag server-side to differentiate in session-management.
Examples
Section titled ExamplesWhen in doubt, look at how “real” services structure their APIs:
Auto-generating documentation (and even code)
Section titled Auto-generating documentation (and even code)- Swagger.io
- Other tools recommended to me
- http://apidocjs.com/
- gelato.io
- apiary.io