Alberto
Alberto I build software

A minimalistic foundation for a Rails REST API

A minimalistic foundation for a Rails REST API

While working on Gazpacho I came to a point where I started to think it would be nice to have an API. Over the years I’ve worked with big Rails applications in which the base API controller had hundreds of lines of code and used dozens of auxiliary classes. But in my case requirements are very simple: I’m in control of all the clients that will consume the API, so I don’t need any sophisticated authentication mechanism and I can live with bare bone error messages without worrying about leaking too much information. This is as simple as it gets and I started to wonder what is the bare minimum I would need to build that simple REST API (I don’t need the added complexity of a GraphQL API, thank you very much). After finishing the API I was so pleased with the minimalism of the result that I thought I’d share it in case someone else find it useful.

The base controller is only ~ 40LOC (19 SLOC):

# frozen_string_literal: true

module Api
  class BaseController < ActionController::Base
    include Api::Authentication

    RECORDS_PER_PAGE = 25

    skip_before_action :verify_authenticity_token

    rescue_from ActiveRecord::RecordInvalid, ActiveRecord::RecordNotDestroyed do |exception|
      render_validation_errors(exception.record)
    end

    private

    def render_validation_errors(record)
      render json: record.errors.as_json, status: :unprocessable_entity
    end

    # Follows RFC5988 convention https://tools.ietf.org/html/rRFC5988
    def paginate(resources)
      resources = resources.page(params[:page]).per(RECORDS_PER_PAGE)

      unless resources.first_page?
        response.set_header("Link",
          "<#{url_for(page: resources.prev_page)}>; rel=\"previous\"")
      end

      unless resources.last_page?
        response.set_header("Link",
          "<#{url_for(page: resources.next_page)}>; rel=\"next\"")
      end

      response.set_header("X-Total-Count", resources.total_count)

      resources
    end
  end
end

It handles pagination and error rendering. For pagination I’m using Kaminari and including the pagination info in Link headers, following the RFC5988 specification.

There’s also a minimal module to implement token authentication (that’s all I really need).

module Api
  module Authentication
    extend ActiveSupport::Concern

    included do
      before_action :authenticate
    end

    private

    def authenticate
      authenticate_or_request_with_http_token do |token, options|
        Rails.application.secrets.api_key == token
      end
    end
  end
end

Thanks to Rails sensible defaults, I can create very slick REST controllers just with that foundation. For instance, a typical controller would be around 40LOC and look like this:

module Api
  class FoodsController < Api::BaseController

    before_action :load_food, only: [:show, :update, :destroy]

    def index
      @foods = paginate(Food::Search.new(params).foods)
    end

    def create
      @food = Food.create!(food_params)
      render "show"
    end

    def show
    end

    def update
      @food.update!(food_params)
    end

    def destroy
      @food.destroy!
    end

    private

    def load_food
      @food = Food.find(params[:id])
    end

    def food_params
      params.require(:food).permit(...)
    end
  end
end

It can perform all the basic REST actions (list, show, create, update and delete) and print responses and validation errors in json format. The views are jbuilder templates, and again quite simple.

And that’s all there is to it! Pretty simple, isn’t it?

Of course, if I start to need more features the code will become more complicated. But I think Gall’s Law applies here too:

A complex system that works is invariably found to have evolved from a simple system that worked.

John Gall, systems theorist

If I ever build a more complex API that works, it will be the evolution of a simple API that works.