Analog Moment

by James Cox-Morton (@th3james)

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.