Introduction

There are several “lightweight” Ruby web & API libraries out there. Sinatra is probably the most known but there are others that are newer, faster, cleaner, more flexible and/or better maintained.

One of those is Roda, created by Jeremy Evans, the creator of the Sequel gem. It started as a Cuba fork but then evolved a lot. It is specially notably by its speed and plugin system, shared characteristics with its sibling Sequel gem, where everything can be overridden.

It is auto-defined as a “Routing tree web toolkit”. Why is that? Because of the way the routes are defined & run. Let me show you an example:

require "roda"

class App < Roda
  plugin :render
  plugin :all_verbs

  route do |r|
    r.root do
      view :index
    end
    r.is  "artist", Integer do |artist_id|
      @artist = Artist[artist_id]
      check_access(@artist)
      r.get do
        view :artist
      end
      r.post do
        @artist.update(r.params[ "artist "])
        r.redirect
      end
      r.delete do
        @artist.destroy
        r.redirect  "/"
      end
    end
  end
end

If you compare with other libraries like Sinatra, instead of setting the @artist variable and checking the access is allowed in all three of the actions, the variables are set as soon as that branch of the tree is taken, and can be used in all routes under that branch. This is why Roda is called a routing tree web toolkit.

More examples of the routing flexibility:

  route do |r|
    # array matchers
    r.on %w[hello hi] do |greeting|
      "#{greeting}, world!"
    end
    # method matchers
    r.is  "hello", method: [:post, :put, :patch] do
      "All your base are belong to us"
    end
    # Regexp matchers
    r.on  "users" do
      r.get /(\d+)/ do |user_id|
        "Your id is #{user_id}"
      end
    end
  end

Roda’s plugin system design is shared with the Sequel gem but also copied in other projects, like in the Shrine file attachment toolkit, Cuba(v3).

You can start with a small, compact core of an application and grow complexity over time and learning as you go, having fine grained control on features and performance.

For instance, wants websockets, checked:

plugin :websockets

route do |r|
  r.get "room" do
    # Matches if it's a websocket request
    r.websocket do |ws|
      ws.on(:message) { ... }
      ws.on(:close) { ... }
      # ...
    end
    # If the request is not a websocket request, we render a template
    view "room"
  end
end

Do you want a JSON API? The json plugin responds with JSON automatically for arrays & hashes:

plugin :json

route do |r|
  r.root do
    [1, 2, 3]
  end
  r.is "foo" do
    { "a" => "b"}
  end
end

But you can customize it easily, for example, adding ActiveRecord relations:

plugin :json, classes: [Array, Hash, ActiveRecord::Base, ActiveRecord::Relation],
  serializer: proc { |object|
    case object
    when Array, Hash
      object.to_json
    else
      Serializer.new(object).as_json
    end
  }

route do |r|
  r.get "albums/recent" do
    Album.recent
  end
  r.get "albums/:id" do |id|
    Album.find(id)
  end
end

The complete list of bundled plugins is huge, more than 90, and there are several community extensions, so you are pretty much covered here. In case you want to do one, this is the structure:

class Roda
  module RodaPlugins
    module MyPlugin
      module ClassMethods
      end
      module InstanceMethods
      end
      module RequestClassMethods
      end
      module RequestMethods
      end
      module ResponseClassMethods
      end
      module ResponseMethods
      end
    end
    register_plugin(:my_plugin, MyPlugin)
  end
end

Performance

An image is worth a thousand words. Compare the request per second and the memory usage:

Pretty close to raw Rack values, but with a nicer DSL.

Maintenance

Jeremy responses in the mailing list are quick & he resolves any issues, too:

(Look Ma, no issues)

Some disadvantages

  • No routes introspection (although there is a plugin that helps with this).
  • Less gems, as many are tightly coupled with default framework.
  • Less community support vs Rails or Sinatra.
  • Some reinventing the wheel may be unavoidable.
  • The routing structure may not be your style (but there is a plugin to use a Sinatra like class route definitions)