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.
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
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
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.