Want to see the full-length video right now for free?
Sign In with GitHub for Free AccessREST 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.
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 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.
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.
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.
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.
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 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.
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.
With each level, we become a bit more RESTful and hopefully our application becomes more robust and maintainable.
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.
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.
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
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.