Michael Anhari

RESTful routes for all the things

Drawing of a screaming stick figure with its fist raised in the air

Today I thought it would be fun to consider what happens to a code base when a team decides to strictly adhere to a RESTful architecture.

Before we begin, let's talk about what REST is.

Representational state transfer (REST)

Per wikipedia, Representational state transfer (commonly abbreviated as REST) is "a software architectural style that was created to guide the design and development of the architecture for the World Wide Web". In other words, it's a style guide for writing code within its constraints. Applications that follow this style guide are said to be "RESTful".

For our purposes in Rails, RESTful architecture involves building controllers that only use the default actions that are generated when you use something like resources :articles to define routes in your application.

These seven actions are as follows:

  1. index - used for displaying a list of a resource (i.e. /articles)
  2. show - used for displaying a single resource (i.e. /articles/1)
  3. new - used for building a new resource (i.e. /articles/new)
  4. create - used for persisting a new resource
  5. edit - used for changing a resource (i.e. /articles/1/edit)
  6. update - used for persisting changes to a resource
  7. destroy - used for deleting a resource

This seems too limited. What gives?

Now, you might be thinking, there's no way these seven actions can cover all of the business logic in an application. And I would agree under the condition where we have one controller per resource (or model) in our application.

For example, let's say our Article model has an upvote_count column for storing likes, and we have a feature request to build out the user experience for incrementing that column.

If we're already using ArticlesController#update to allow the author to update the entire article, then how can we update this column without ending up with a bunch of conditional statements (which increase complexity significantly) in our update method?

Create smaller controllers that do not map to your resources one-to-one

Controllers, after all, are merely a liason between our application and it's data layer. They do not need to map to our models one-to-one.

Let's shift our thinking and try considering the user action as the resource. In this case, a user is upvoting an Article. What if created a brand new controller for this action instead of trying to jam this into our ArticlesController#update method or creating a non-RESTful route like ArticlesController#upvote?

We might end up with something like this:

# config/routes.rb
resources :articles do
  resources :upvotes, only: [:create]
end
class UpvotesController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    if @article.increment!(:upvote_count)
      redirect_to @article
    end
end

It might feel weird using a create action to update something on the backend, but I assure that this is perfectly reasonable.

Impacts on our code base

Developing Rails applications that adhere to REST as close as possible has led to code that is easier to maintain in my experience. The limitations force you to break parts of your application into smaller pieces, which is usually a great trade-off to make. Smaller controllers are easier to test, manage, and change. REST also results in the homogenization of your code base and makes it easier for a team to work on together.

Newsletter

I'm working on sending out a weekly newsletter. I'll make it as easy as possible to unsubscribe at any time.