My Journey Through REST
How we built our first API
Here at Skillshare, I was given the opportunity to design and implement the API that powers our mobile applications. I chose to go with designing an API that exhibited RESTful properties. When doing more research on each of the constraints of REST, all of them except for Code on Demand ended up aligning quite well with how I wanted the API to work. But, when doing more research on the Uniform Interface constraint, I was unsure about how much of it we should implement.
You might find yourself in a similar situation if you’re looking to implement a REST API of your own. So, let’s dig into each of the properties of the Uniform Interface constraint, which we ended up implementing, and how they have affected our API. This should give you a great sense of whether or not all of these properties are right for yours.
A very quick introduction to REST (REpresentational State Transfer). REST is an architectural style that can be applied to Web APIs. It describes six constraints to be applied to your architecture. Given that REST is a style, it’s not necessarily true that you need to implement each constraint fully. As mentioned above, this can make choosing which constraints, and how much of each of them are applicable to your Web API, a difficult task.
Here they are, for reference:
- Client-server: A uniform interface separates clients from servers. As an example, clients aren’t concerned with data storage and servers aren’t concerned with user state or user interface.
- Stateless: No client state being stored on the server between requests. Each request from any client contains all the information necessary to service the request, and session state is held in the client.
- Cacheable: Responses must implicitly or explicitly define themselves as cacheable, or not, to prevent clients from reusing stale data.
- Layered System: A client can’t tell whether it is connected to the end server, or to an intermediary along the way. These intermediary servers can improve system scalability by enabling load balancing and shared caches.
- Code on Demand (optional): Servers can temporarily extend or customize the functionality of a client by the transfer of executable code.
- Uniform Interface: Identification of resources, manipulation of resources through representations, self-descriptive messages, and hypermedia as the engine of application state (HATEOAS).
For a more detailed explanation of each of these constraints, check out the Wikipedia article on REST.
For us, we’re interested in the Uniform Interface constraint, so let’s dive deeper and discuss how we decided to handle each of its properties:
Identification of resources
Individual resources are identified in requests using URIs as resource identifiers. The resources themselves are conceptually separate from the representations that are returned to the client. For example, the server may send data from its database as HTML, XML or JSON, none of which are the server’s internal representation.
This one made a lot of sense to me. You should be defining your URI structure based on nouns (/users) describing resources within your system versus SOAP-like URIs that are generally verbs (/users/create) acting on those resources. When defining the URI structure based on nouns only, we can allow for the HTTP method to specify what you want to do to the resource. In this example with users, sending a POST request with some data to the /users endpoint signifies that you would like to create a new user versus having to include that information in the URI itself (/users/create).
We also decided to take being able to show multiple representations for a given resource to the next level. We did so by implementing a custom vendor MIME type that allows the client to specify the view of the resource they would like to see. This includes both the specific view name as well as the version of that view.
Having your URI structure be predictable allows for clients to act on resources (or resource collections) in a predictable way as well.
It made our routing of URIs to Controller action handlers very simple and generic:
GET /users : actionIndex
GET /users/123 : actionShow
POST /users : actionCreate
PATCH /users/123 : actionUpdate
DELETE /users/123 : actionDelete
These same handlers are implemented on all of our Controllers.
The impact of implementing custom Vendor MIME types in order to allow a client to specify the view and version of the resource was huge. It has allowed us to put our API Versioning capabilities completely in our view rendering system. We, for example, didn’t have to implement multiple action handlers for each version. We instead just give our view rendering system the resource, view, and version at which to render the resource.
As an example, we can request a full representation of a class at version 1.0 and we can also request a summary representation of a class at version 1.0 at the same URI, but with different Accept headers:
Accept: application/vnd.skillshare.class+json; version=1.0;
title: "My Class",
description: "Best Class Ever",
Accept: application/vnd.skillshare.class-summary+json; version=1.0;
title: "My Class",
description: "Best Class Ever"
This is a contrived example, but it shows how simple it is for a client to request different representations of a given resource.
Manipulation of resources through representations
When a client holds a representation of a resource, including any metadata attached, it has enough information to modify or delete the resource.
We implemented this because it makes sense that a client should be able to act upon a resource based on information retrieved from the API and not have to rely on any external documentation.
This improved the speed at which we were able to develop our iOS and Android applications due to the fact that our engineers were able to rely on a consistent way of manipulating resources through the API. As an example, the way to delete a user is by performing a DELETE request on its resource URI (DELETE /users/123). This pattern holds true throughout the API.
Each message includes enough information to describe how to process the message. For example, which parser to invoke may be specified by a MIME type. Responses also explicitly indicate their cacheability.
We implemented this because our clients need to be able to determine what format responses are being returned in and whether or not they are cacheable.
Having our API responses including the Content-Type header denoting what format the response is in as well as caching headers denoting whether or not the response is cacheable has been great. We haven’t gotten into caching in the API all that much but sending accurate caching headers is definitely important.
Hypermedia as the engine of application state (HATEOAS)
Clients make state transitions only through actions that are dynamically identified within hypermedia by the server (e.g., by hyperlinks within hypertext). Except for simple fixed entry points to the application, a client does not assume that any particular action is available for any particular resources beyond those described in representations previously received from the server.
The idea of having each response contain a list of links to related resources in the API seemed a bit weird to me at first. But, when thinking of it from the standpoint of hyperlinks within hypertext, it really started to come together. For example, if a web page had no links on it, how would you even know how to get to the other pages on the website? You wouldn’t. If we liken this to not including links to related parts of the API, someone who is developing a client application that uses your API would be forced to look up your API documentation in order to know how to use it. This goes against the self-discoverability property of REST.
This is actually the property we were most on the fence about implementing. However, it has had an extremely positive impact on our API, and I would strongly suggest you consider implementing it.
To demonstrate how we implemented this, here’s an example response for a user in our API:
"title": "Patrick Danford"
"title": "My Classes"
"title": "Patrick Danford's Wishlist"
"title": "Patrick Danford's Completions"
"title": "Patrick Danford's Rosters"
"title": "Patrick Danford's Votes"
"title": "Patrick Danford's Projects"
As you can see, there is a _links attribute which shows all of requests that you can make that are related to the requested user resource. This is amazing! Not only do our clients not have to figure out what the URI is to retrieve a user’s projects, it doesn’t even have to know that there are project resources in the REST API until it requests the user resource.
The title attribute on the _links has also proven very useful for our mobile clients. Instead of having to parse through the response and pick out the user’s first_name and last_name, they can instead just use the user’s title.
Again, given that REST is actually an architectural style, it proved quite difficult for me to determine which properties to actually implement within our API. I hope the above description of how the Uniform Interface constraint impacted our API will allow for an easier decision-making process when/if you go on to implement a REST API of your own. I also strongly encourage you to consider implementing the Manipulation of resources through representations and HATEOAS properties of the Uniform Interface constraint as they have proven very useful for us here at Skillshare.
p.s. Want to work with us? Our Engineering team is hiring.