Everybody wants a super fast API, right? There are a lot of resources on how to use fragment caching to speed up responses, but there’s a much easier way that is often ignored - conditional GET requests. These things are simple, easy to use, and built right into the HTTP protocol.

Conditional GET

Conditional GET requests allow for simple caching of requests. Rails provides this ability by using the ConditionalGet middlware. Worth noting – this middleware is included in api-mode in Rails 5.

The mechanism is fairly simple – the first time a client asks for a response, the server will send a Last-Modified header with an appropriate timestamp (for example, the value of the updated_at column of an ActiveRecord object). Then, the next time the client asks for the same data, the client provides an If-Modified-Since header, which contains the Last-Modified timestamp from the previous request. The server can then figure out whether or not to send a cached version of the data or not.

We can easily set this up using the stale? method in our controllers:

    def show
      @person = Person.find(params[:id])

      if stale?(last_modified: @person.updated_at)
        render json: @person
      end
    end

The stale? method simply calls fresh? on the request object to see if the cache is still valid by looking at the appropriate headers. You can look at the source of this method here to see what is going on under the hood.

If the cache is still valid, stale? will return false and will call head :not_modified for you. So your users will get a 304 Not Modified response. If the cache is not valid, stale? will return true. At that point it’s up to you to do the right thing. In the example above, we simply render some json.

You can also use an ETag (“Entity Tag”) to accomplish nearly the same thing. The major difference is the ETag is passed back to the server using an If-None-Match header instead of If-Modified-Since:

    def show
      @person = Person.find(params[:id])

      if stale?(etag: @person)
        render json: @person
      end
    end

An ETag is essentially a hash of an object. If the hash has changed, then we know the object has changed as well. ETags are especially helpful in the context of collections, where you might want to invalidate the cache if any one of several objects has changed. You might do something like this in your controller’s index action:

    def index
      @people = Person.all

      if stale?(etag: @people)
        render json: @people
      end
    end

One drawback of ETags is that they are used to track people on the web, and many privacy-aware users will delete or block them. So you might want to use last_modified timestamps instead of ETags if you think this might be a problem.

You can also pass an ActiveRecord object straight to stale?, or a collection or any object that responds to #maximum. The stale? method is provided by ActionController::ConditionalGet, and the source has some more info about how to use it.

And that’s pretty much it! You have now dramatically sped up your API with only a few lines of code.