Fixing common REST mistakes by using HTTP effectively
HTTP is a well designed protocol which, used properly, effectively caters to the needs of developers building REST APIs. Unfortunately, despite using HTTP every day, we developers don't always take the time to understand and use what's available to us. Here we'll look at some common REST mistakes and how we can properly use HTTP to fix them.
A quick word about URLs
URL is one of those Three Letter Acronyms we use so often we forget what the words behind the acronym are telling us. Uniform Resource Locator is extremely descriptive - put another way, URLs are the address of a noun (e.g. a blog entry, a collection of products etc). Note that this description does not say anything about verbs, formats, or versions. However, that's not stopped eager developers from cramming those concepts into URLs.
Mistake #1 - Putting actions into URLs
http://example.com/post/12/create-comment
URLs address nouns, but this URL contains a verb: "create"
Solution: Use HTTP Verbs to support multiple operations on nouns
You should prefer having a single URL for each noun and support different operations through different HTTP verbs. In this case, by making the singular comments URL support both GET to list comments and POST to create new ones:
// Get all comments on post 12
GET http://example.com/post/12/comments
// Create a new comment on post 12
POST http://example.com/post/12/comments
Mistake #2 - Putting formats in your URLs
https://example.com/product/blue-robot.html
https://example.com/product/blue-robot.json
https://example.com/product/blue-robot/as_yaml
Again, the issue here is that we have multiple URLs for the same noun, where the only difference is the desired representation. The format of the response (HTML, JSON, YAML) doesn't belong in the URL, it should be determined on a per-request basis.
Solution: Use the HTTP content negotiation headers to choose representation
If you want to support multiple representations of a given resource, do so by handling the Accept HTTP header and returning the Content-Type HTTP header in your response.
// Request asks for text/\*
> GET http://example.com/product/blue-robot
> Accept: text/\*
// Response returns body as HTML and the header:
< Content-Type: text/html
// Request asks for application/json
> GET http://example.com/product/blue-robot
> Accept: application/json
// Response returns body as JSON and header:
< Content-Type: application/json
Mistake #3 - Putting versions in your URLs
Here's another common example of unnecessarily creating multiple URLs for a given resource.
http://example.com/v1/products/red-car
http://example.com/v2/products/red-car
These two URLs are different "versions" of the same resource.
Solution: Use custom HTTP headers to support version negotiation
Unfortunately, this is something that HTTP doesn't specifically address, but luckily there's sufficient flexibility in HTTP for us to RESTfully handle versioning.
One strategy is to support a custom Accept-Version
header:
// Request asks for v2
> GET http://example.com/products/red-car
> Accept-Version: v2
// Response return v2 representation
< HTTP/1.1 200 OK
< Version: v2
Mistake #4 - Putting statuses in your response body
Breaking from URLs for our last tip, here's another common anti-pattern that fails to use HTTP effectively.
> GET http://example.com/products/red-car
> < HTTP/1.1 200 OK
> < {"status": "error", "message": "No such product"}
> POST http://example.com/posts
> < HTTP/1.1 200 OK
> < {"status": "error", "message": "Unauthorised"}
Both of these scenarios (content missing and authorisation failure) can be described using status codes, but instead the error messages are dumped into the response body.
Solution: Always use the most appropriate HTTP status code, add detail in the body where necessary
Always use the most descriptive HTTP status code. There's nothing to stop you from adding more detail about the error to the body of your response, but start with the correct HTTP status and try to avoid redundancy.
> GET http://example.com/products/red-car
< HTTP/1.1 404 OK
< {"message": "No product with slug 'red-car'"}
> POST http://example.com/posts
< HTTP/1.1 401 OK
< {"message": "Authentication not provided"}
Conclusion
Hopefully this advice helps you to leverage HTTP properly to build better REST interfaces, but also acts as a reminder to take the time to effectively learn the tools you might already think you know because you use them every day.