Video

Want to see the full-length video right now for free?

Sign In with GitHub for Free Access

Notes

REST is the core architectural pattern we use to build our Rails applications, but it's not always clear what exactly is and isn't REST. Tune in as Matt Sumner joins Chris discuss exactly what REST is, and how best to embrace it in your Rails apps.

What is REST?

Representational State Transfer (REST) is an architectural pattern. The specifics of what exactly is and isn't REST is a hotly debated topic, but at it's core it defines a set of constraints for building robust distributed system (web applications in our case). Rails embraced REST in version 2, and since then it has been core to how we write and think about the structure of our web applications.

It was originally outline by Roy Fielding, one of the principal authors of the HTTP specification, in his 2000 doctoral thesis. Rather than being a brand spanking new idea, REST is a considered look at the success of the World Wide Web, and a summary of some of the constraints and the overall architectural approach that led to this success.

REST Constraints

REST itself is defined in terms of a number of constraints. A system is deemed to be "RESTful" if it conforms to these constraints. The post What RESTful Actually Means post on Recurse Center's blog does a great job of describing what REST is, and isn't, specifically introducing a number of these constraints.

Client server

This may seem obvious, but the client-server constraint gives rise to more interesting constraints such as the request response cycle and statelessness.

The client in our case is typically the browser, although it could be a utility like cURL. The server is our web server and typically the associated web application.

Stateless

For a system to be RESTful, it must be stateless. This does not mean that the client and server cannot have any knowledge of each other, but simply that the entirety of an interaction is encapsulated in the request and response exchange. This is in contrast to a technology like websockets where the client and server maintain a connection between request response cycles.

Statelessness is one of the key aspects that allows for a system to grow horizontally (add more servers, rather than increasing the resources on a given server). This ability to scale horizontally is in turn a key feature of what allows web based applications to scale to handle huge volumes of traffic.

Identification of Resources

For any given entity within our application, we should be able to access that entity at a stable identifier, a URL in the case of our web applications. Note, this does not necessarily map to the entities in your database, but instead this constraint is about determining what the entities or "resources" your application exposes, and then mapping to them stable URLs. Cool URIs Don't Change, as they say.

Practically, Rails handles most of this for us. The key is just to lean into Rails' REST foundation by making use of resources and resource for defining our routes, and ensuring we have routes like /posts/10/comments, and not /getCommentsByPostId as you might see in an RPC based system.

Manipulation through representations

This constraint breaks down into two pieces, the manipulation aspect, and the representations. "Manipulation" here refers to using the built in HTTP verbs like GET, POST, PUT, etc to manipulate our resources, rather than encoding these actions in the URL.

The second portion regarding representations deals with the idea that we may want to see an HTML page for a given resource, or we may want to see a JSON response. These both should be accessible at the same URL as we are still dealing with the same identified resource, we simply want a different representation of it. Rails gives us a great language for providing various representations of our resources with the respond_to method in controllers.

Hypermedia and HATEOS

Hypermedia and or Hypermedia as the Engine of Application State, aka HATEOAS, is likely the least well applied constraint of REST, especially in the world of APIs.

Hypermedia, while sounding complex, is really just saying that we embed links within our documents. Further, HATEOAS implies that these links are the mechanism for exploring and interacting with / manipulating the resources within our application.

It turns out the while this is common for HTML web applications, it is generally considered to be less useful within an API. GitHub's API is one of the few examples I've seen of an API that actually embraces Hypermedia and HATEOAS. Specifically, the JSON representation for the thoughtbot resource includes a number of links to the associated resources. By following these links, and even using the HTTP verbs to manipulate them, we find ourselves with a self-descriptive Hypermedia API. Neat, but actually very rare in most APIs.

At the end of the day, Mr. Fielding has been very clear that REST APIs must be hypertext-driven, and as such almost no one can actually claim to have a RESTful API... but that's mostly OK. Let's not throw the web app out with the bath water just because we missed this point. The other constraints of REST listed above still provide immense value, even if we miss out on being truly RESTful due to non-HATEOAS API design.

Pragmatic REST

So in the end it turns out most of us are not really building RESTful systems, but again, that's OK. The idea of achieving different levels of RESTfulness is covered well in the Richardson Maturity Model of REST post by Martin Fowler, documenting a model developed by Leonard Richardson.

  • Level 0: RPC calls and sadness
  • Level 1: Resources (nouns in our URLs!)
  • Level 2: HTTP verbs (they're there, why not use em'?)
  • Level 3: Hypermedia Controls

With each level, we become a bit more RESTful and hopefully our application becomes more robust and maintainable.

REST in Rails

Rails provides us with a great foundation for building RESTful web applications, but it's very easy to introduce non-restful actions into our application and fall into a slippery slope of non-RESTfulness.

Fully Virtual

Here we have a :video resource (one of the videos on Upcase), and we want to allow users to download the video. A common approach would be to add a member route to the :videos resource like so:

resources :videos do
  member do
    get :download
  end
  # ...
end

Which then maps to a download action in our VideosController:

class VideosController < ApplicationController
  # ... RESTful actions omitted

  def download
    track_downloaded
    redirect_to video.download_url(download_type)
  end

  private

  # ... help methods omitted as well
end

On it's own this likely doesn't seem like a problem, but it can quickly lead to bloated controllers with many methods, no logical mapping between routes and the controller that handles them, and generally messes with the structure of your application.

Instead, you can see the actual approach we took in the DownloadsController in Upcase. We defined a nested singular resource of :download within the :videos resource block, scoping it to only support the show action:

resources :videos do
  resource :download, only: [:show]
end
class DownloadsController < ApplicationController
  before_action :require_login

  def show
    track_downloaded
    redirect_to video.download_url(download_type)
  end

  private

  # ... private methods omitted

While the code change is minor, the conceptual change of identifying a Download as a resource within our application is significant and meaningful. Note there currently is no Download model or table in the database, this resource exists only at the routing / controller layer of our application. Rather than this being a problem, this is actually a very good thing. This is the whole reason we have these layers, so that we can build the desired abstractions at each layer, hiding away implementation details.

Active Model (not AR) Backed

Similar, there was a recent discussion around an Invitations example from the guides that also captured this idea. The question was about the logic behind the thoughtbot Rails style guide rule:

Avoid member and collection routes.

The specific question had to do with modelling an Invitation, and wondering how to handle the invitation being accepted, without using a member route and adding and accept action to the controller.

It turns out, Upcase has exactly this data model. To handle accepting an invitation we define a new resource, an Acceptance and provide routing and an AcceptancesController for it. Unlike the Download example above, the logic around an Acceptance was sufficiently complex that we also introduced an Acceptance Model, but note that this model is not backed by a DB table. Instead, it includes ActiveModel::Model to allow it to act as a form object.

  resources :invitations, only: [:new, :create, :show] do
    resource :acceptance, only: [:create]
    resource :rejection, only: [:create]
  end

Beyond REST

We're sold on REST, and all applications we build here at thoughtbot do follow the majority of REST. That said, one of the interesting thing out in the world that is purposefully non-RESTful are GraphQL, Falcor, Om Next, and similar projects focused on supporting mobile clients. They take a very different approach to building APIs, but with the newer constraints of mobile clients, high latency and slow networks, they represent potentially very interesting alternatives to RESTful architecture.