There are different opinions out there on best practices for developing RESTful services. Projects have different needs, developers have different opinions and HTTP has its limitations just like most other technologies.
In this blog I list some of the best practices and guidelines sourced from the World Wide Web, sprinkled with my own opinion.
Key API requirements
According to Vinay Sahni's article, there are some key requirements for a good REST API.
- Use web standards when they make sense. Or the inverse, do not use (certain) web standards for your API if the don't make sense in your context.
- Your API should be friendly to the developer (your API's client) and be browser-explorable.
- It should be simple, intuitive and consistent.
- It must be efficient and accomplish the task easily.
- Your REST API must match the domain you want to expose and be flexible to change.
API design best practices
Use plural nouns, not verbs: /books and not /getbooks
REST as an architectural style differs from SOAP webservices in that it works on resources, using http mechanisms like POST to indicate an action to be taken. Instead of the action being described by the API (/getbooks), the action is a combination of the resource (/books) and the http method (GET).
It is important to highlight that we use the plural form. It keeps the API consistent and simple, as you don't need to worry about pluralisation of resources.
JSON vs XML vs...
XML works well for inter-system contracts or when you really need to be strict on the structure (you can also use JSON schema).
Use other return types based on your needs. Octet stream works for returning documents or images directly.
GET and query parameters should not alter state
You wouldn't have an API method called getBook(bookId) to alter the state of the book, so don't do it for your REST service either. GET and query parameters should not alter the state of the resource.
POST vs PUT
Use a POST when creating a new entry. A call to POST will, every time, create a new resource with a new identifier (if applicable).
PUT on the other hand creates a new entry at first, and updates it thereafter.
POST books/book (with appropriate form data or json)
PUT books/book/123 (note the addition of the ID)
Use sub-resources for relationships: /library/books/
When you need to show sub-relationships, build it in to the URI. The rule of thumb here is to not go deeper than 3 levels with this. If you need to go deeper, maybe create query aliases or provide more direct access to the lower-level resources.
Use HTTP headers to indicate serialization formats
Make use of Content-Type to indicate the (return) type of the content, and Accept to indicate what the client wants to accept.. Some developers modify the Content-Type to include versioning information. Nazar Annagurban shows how you can use Content-Type to indicate versioning (maybe for a minor version): application/myapp.v1+json.
Use HATEOAS (hypermedia as the engine of application state) to assist navigation
Although not necessarily used everywhere, there are a lot of proponents for adding urls to your results to aid the client in navigating your resources. For example, URLs are added as part of your JSON to allow pagination or accessing related resources. Again, use web standards where they make sense.
Provide filtering, sorting, field selection and paging for collections
Make it easy for the client of your API to browse collections of resources.
- filtering: filter resources on some field, for example GET /books?category=horror
- sorting: sort resources in forward or reverse order, for example GET /books?sort=title (sort ascending on title) or GET /books?sort=-title (sort descending on title)
- paging: if the collection is large, allow pagination using offset and limit, for example GET /books?offset=20&limit=20 to get the next 20 books, starting at book number 20. A good idea is to add the URIs (HATEOAS) for paging to navigate previous, next and so on.
- field selection: For resources with lots of fields, allow the client to retrieve a subset of fields, for example GET /books?fields=title,isbn
Version your API in the URL: /myapi/v1/
It is seen as best practice to add a simple version in the URL so it's easy to browse your API in the browser between versions. You can also add the version number in the header (see Nazar Annagurban's article). Some APIs add the major version in the URL and the minor version, or a timestamp, in the header.
Sending multiple parameters (range of values)
When you need to specify a range of values in a query, you have two options:
- application/x-www-form-urlencoded standard: /books?titles=title1&titles=title2
- Widely used JAX-RS-happy standard: /books?titles=title1&titles=title2
So, don't do /books?titles=title1,title2 or something along those lines.
Handle errors with HTTP status codes and return results
When something happens, good or bad, use the HTTP status codes. Also return a payload listing the error. The returned 'error' JSON should be consistent across your API.
Some common uses of Http error codes are listed below.
200 OK - Response to a successful request. Use this whenever your GET, POST, DELETE etc was successful.
201 Created - Response to a POST that resulted in a successful creation. You need to also return a response body pointing to the unique ID or location (url) where the new item can be retrieved.
204 No Content - Response to a successful request that won't be returning a body (like a DELETE request or a GET query with no results). I've used this as well when no results were found in a search query.
304 Not Modified - This means the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.
307/308 Temporary/Permanent Redirect - Use these if you rolled out a new version of your service and want the client to go there instead (older service is now depricated but still functioning).
400 Bad Request - The request is malformed or something on the client is perceived to be in error.
401 Unauthorized - Authentication is required by the client. After authentication, the request may be executed successfully.
403 Forbidden - This means the client was authenticated but not authorized.
404 Not Found - The requested resource couldn't be found (but may be found in the future).
405 Method Not Allowed - When e.g. a POST is sent to a resource that only supports GET.
410 Gone - See also 307/308 - you can use this to indicate your service is now removed, not just deprecated.
415 Unsupported Media Type - If the client does a PUT or POST, with Content-Type that the service does not support. For example, you're posting xml while the service wants JSON.
422 Unprocessable Entity - Used for validation or semantic errors.
429 Too Many Requests - When a request is rejected due to rate limiting.
500 Internal Server Error – When your service has an internal error (some exception you can't handle gracefully), use this code. Your service should trace-log it out but send very little information to the client to avoid exploitation.
For more detail you can have a look at the wikipedia list of http status codes.
Although there are different opinions on the RESTful-ness of a service, the fact is you're OK if
- You think Resources (plural);
- You think Http standards (GET, PUT, Headers, Error codes, queries etc) to be the engine of your service and describe the actions;
- You version your service.
And finally, don't overkill your solution if it's not required.