There are a variety of web frameworks and libraries that help us build and consume HTTP-based APIs quickly and easily. Whether you are new to HTTP or experienced, let’s remind ourselves of the power of HTTP to help us design robust REST-based APIs. We will lean heavily on the various HTTP-related specifications, so I will provide links to the specification where the topic is referenced in case you want to research the concepts further.
The basics: URLs, HTTP methods, and headers
Uniform Resource Locators, or URLs, is an identification system for the location of resources on the Internet. URLs are powerful, as they allow APIs to be referenced across any accessible server. Additionally, the URL’s path provides for a hierarchical structure, affording us the opportunity to organize our resources into logical groupings. It is the URL that often assigns API ownership (via the hostname) and resource structure (via the path).
HTTP methods indicate the action to take on a particular resource. Examples include: GET, POST, PUT, DELETE, and HEAD. The method requested informs the server whether we are requesting data or modifying data.
Summary: When we combine an HTTP method with a URL (sometimes called an endpoint), we inform an API server what kind of action to take and the resource to take the action upon.
Status codes, sometimes referred to as response codes, provide the client with a numeric code that indicates how the client should proceed based on the result of the request. Status codes are grouped into families, indicating if the request was a success (2xx), the request is being redirected (3xx), failed due to client error (4xx), or failed due to server error (5xx).
Summary: When properly implemented by API providers, consumers should first check the status code in the response to determine the result of the request. Our client code isn’t required to parse the response payload to determine if a success or failure message was returned.
Finally, HTTP headers are name/value pairs that may be passed from the client (as part of an HTTP request) to inform the server about the request and the client’s capabilities, or from the server (as part of an HTTP response) to inform the client about the results of the request that was processed. HTTP headers are designed to separate the details about the message from the message (the body of a request/response). The separation of concerns between the header and message prevents our API designs from mixing protocol-level concerns with the message payloads our API exchanges.
Summary: HTTP headers separate the client/server interaction from the API design. Doing so empowers API designers to focus on the API capabilities to be delivered rather than mixing protocol and resource representation within request and response payloads.
Building upon this foundational knowledge of HTTP, let’s next explore two specific headers that are useful for web-based APIs.
Content negotiation: Supporting multiple content types
Content negotiation allows clients to request specific content type(s) to be returned by the server. With content negotiation, we enable a single endpoint to support different types of resource representations (e.g. CSV, PDF, PNG, JPG, SVG, etc). While many APIs only offer one content-type, such as JSON, they may support multiple content types per endpoint, allowing consumers to select the best one based on the circumstance.
The client may request any content type it prefers using the Accept header, including specifying */* if the client can accept anything (common for browsers). Clients may suggest more than one content-type, using quality values (or “qvalues” for short) to indicate an order of preference. The API server will review the Accept header and return the response using the content type that matches both what the server supports and what the client requested. If the server cannot respond with an accepted content type, it will return a 415 Unsupported Media Typeresponse code. Let’s look at an example request:
GET https://api.example.com/projects HTTP/1.1 Accept: application/json;q=0.5,application/xml;q=1.0
Notice the use of the qvalue in the example above. The client accepts multiple content types: XML with a high preference and JSON with a lower (but also acceptable) preference. The qvalues provide a relative weight, or preference, for the content types is would like to have relative to the others listed. If a qvalue isn’t specified, the default weight assigned is 1.0. The use of qvalues allows our API client code to support a specific type, perhaps JSON for improved parsing performance, but XML is also acceptable in the case where the API provider only supports XML.
Example response:
HTTP/1.0 200 OK Date: Tue, 16 June 2015 06:57:43 GMT Content-Type: application/xml <project>...</project>
The server responds with the best match content type (assuming it has been coded to support one or both of these content types) using the Content-Type response header. We can use the same approach to add support for CSV and PDF content types for some or all of our endpoints.
Summary: Content negotiation extends our API beyond a single format, such as JSON or XML. It allows some or all endpoints of an API to respond with the content type that best meets the needs of the API consumer. We use the Accept header in our request to list the preferred content types. The server informs the client of the content type in the response payload using the Content-Type header, allowing the client to apply the proper parsing library to process the response.
Language negotiation: Supporting multiple languages
Language negotiation allows APIs to support multiple languages. Similar to content negotiation, the API client sends the Accept-Language header to the server. If the API server supports multiple languages, it can return a response payload in one of the languages supported by the client, indicated by the Content-Languageresponse header.
Note: When it comes to browser-based language negotiation, there can be situations that result in it not working properly. This has caused some API providers to shy away from using HTTP language negotiation.
Summary: If an API client knows which language a user prefers and the API supports it, we apply language negotiation to localize our API responses, including text, currency, etc.
Hypermedia Links: It’s all relative
A hypermedia API is one driven by self-descriptive links that point to other, related API endpoints. Often, these links point to other resources that are related, e.g. the owner of a project, or to relevant endpoints based on the context of the consumer. To take advantage of hypermedia links, we build upon the core principles of HTTP by assigning unique URLs to our resources.
Below is a Github example of embedding hypermedia links within a resource representation:
GET https://api.github.com/users/launchany { "login": "launchany", "id": 17768866, "avatar_url": "https://avatars3.githubusercontent.com/u/17768866?v=3", "gravatar_id": "", "url": "https://api.github.com/users/launchany", "html_url": "https://github.com/launchany", "followers_url": "https://api.github.com/users/launchany/followers", "following_url": "https://api.github.com/users/launchany/following{/other_user}", "gists_url": "https://api.github.com/users/launchany/gists{/gist_id}", ... }
The links provided in this example response help the client navigate the API as it needs further details about the user, e.g. the user’s gravitar image. The client doesn’t need to be concerned about where images are hosted and if the image hosting service changes over time – it simply uses the provided URL when displaying the gravitar. Our API clients become more resilient to change and also benefit from no longer needing to hand-craft URLs.
APIs that need to support pagination are commonly designed using offset and limit parameters to request subsequent pages after the first set of results are returned. For data sets that change often, a better approach may be to use a cursor to avoid skipping or duplicating results. Our pagination strategy may need to change and we don’t want to cause our API clients to suddenly break if we change our pagination strategy.
How do we design a pagination approach that works for both of these cases and perhaps others not currently known? We can use hypermedia links once again, but this time to guide API clients on navigating through results, not just referencing external images or other parts of the API. Let’s look at an example:
{ "_links": { "self": {"href": "/projects" }, "curies": [{"name": "cofrel", "href": "https://api.example.com/hypermedia/rels/{rel}", "templated": true }], "next": {"href": "/projects?since=d266f6cd-fddf-41d8-906f355cbecfb2de&maxResults=20" }, "prev": {"href": "/projects?since=43be807d-d518-41f3-9206- e43b5a8f0928&maxResults=20" }, "first": {"href": "/projects?since=ef24266a-13b3-4730-8a79- ab9647173873&maxResults=20" }, "last": {"href": "/projects?since=4e8c74be-0e99-4cb8-a473- 896884be11c8&maxResults=20" }, "cofrel:find": { "href": "/orders{?id}", "templated": true }, }, "currentlyActive": 4, "currentlyArchived”: 24 }
With our navigational hypermedia links, API clients are able to follow the results in any direction through the results. They do not have to be concerned about the pagination style and how to compose the URL properly or if the style has changed. This is extremely powerful and even mimics how we use the web today when we perform a Google or Bing search.
This approach to managing pagination has the added benefit of conveying server-side state to the client as well. If we are on the first page of results, the ‘prev’ link won’t be provided and the UI can reflect that with a disabled ‘prev’ link. Similarly, if we are on the last page of results, the ‘next’ link won’t be provided. This is an application of the HATEOAS constraint.
HATEOAS (“Hypermedia As The Engine Of Application State”) is a constraint within REST that originated in Fielding’s dissertation. The primary advantage of HATEOAS is to avoid sending boolean fields or state-related fields that require the client to interpret them and decide what action(s) can be taken next. Instead, the server determines this ahead of time and conveys what can and cannot be done by the presence or absence of the links provided.
Summary: Hypermedia and HATEOAS are powered by URLs assigned to resources within an API. These links can convey state to clients, informing users what can and cannot be done at a particular time, based on their permissions and the state of the data. It is the the use of HTTP and URLs that allow us to build evolvable applications through hypermedia and HATEOAS.
Wrap-up: Part 1
In this article, we have only examined a small portion of what is useful for our APIs from the HTTP specification. With just these items in hand, we start to realize that HTTP is a robust protocol that supports a variety of our needs. In part 2 of this series, we will dig deeper into HTTP to see how we can layer additional capabilities into our APIs to make them more robust and scalable.
Special thanks to Darrel Miller and Matthew Reinbold for reviewing this article.