Introduction to Roda

When I decided that I want to move away from Rails, I have investigated
and experimented with a lot of other Ruby web frameworks (Sinatra, Grape and
Lotus), but this one really stood out in every regard, and it became my
framework of choice. That’s why I want to show it to you.

Roda is a web framework built on top of Rack, created by Jeremy Evans, that
started as a fork of Cuba and was inspired by Sinatra. The following is the
simplest app you can make in Roda, which returns “Hello world!” for every
request:

# config.rurequire"roda"Roda.route{"Hello world!"}runRoda.app

Let’s explain what the official Roda description means:

Roda is a routing tree web framework toolkit.

The routing tree

Roda (and Cuba) have a very unique approach to routing compared to Rails,
Sinatra and other Ruby web frameworks. In Roda you route incoming requests
dynamically as they come.

Let’s see what’s going on here. First, we subclass Roda (the same way we
subclass Sinatra::Base or Rails::Application). The route block is called
whenever a new request comes in. It is yielded an instance of a subclass of
Rack::Request with some additional methods for matching routes. By
convention, this argument is named r (for “request”).

Firstly, if the path of the request starts with “/albums”, the request will be
matched by the r.on call, calling the given block. Next it will be matched by
the r.is call if the path continues and ends with “/recent” (r.is is a
terminal matcher). Finally, r.get will match only GET requests. Altogether,
this route block handles GET /albums/recent requests by assigning a list of
recent albums.

The reason why this is called a “routing tree” is because routing is
branched. If the request doesn’t start with “/albums”, the whole r.on
"albums" block (“branch”) is immediately discarded and routing continues to
next branches.

Ok, so far this looks like a flavor of Grape with a weird syntax. But the
difference is that the route block is called each time a request is made, so
this routing is actually happening in real-time. This means that you can
handle the request while you’re routing it. And this is where it gets cool.

Since all of these 3 “/albums/:id” routes have to first find the album, we can
assign the album as soon as we know that the path is going to be “albums/:id”,
and then we reference it anywhere down that branch. We can also require login
for any “/albums*” requests. In other web frameworks you would solve this with
before filters in order to avoid duplication, but that splits code that should
be together into different lexical scopes, making it harder to follow. With
Roda you can write DRY code in a very readable way.

This is a new concept, and it opens a whole new world of routing possibilities.
From other web frameworks we are used to routing only by the request path and
method. But why not also route by request headers or parameters?

classApp<Rodaplugin:header_matchersplugin:symbol_matchersroutedo|r|# If the "Authorization" header is set, we return that user's postsr.get"posts",header: "HTTP_AUTHORIZATION"do@posts=current_user.postsend# Otherwise we return all postsr.get"posts"do@posts=Post.allend# Matches "/" if the "mobile" query parameter is passed inr.rootparam: "mobile"do# Matches "?mobile=true"end# We can do whatever we want, even throw in some conditionalsifcurrent_user.admin?runMonitoringApp# routes the request to the Rack applicationendendend

As you can see, Roda’s routing tree is very powerful, because you have the
complete control. But if you don’t like it, you can just use Roda like
Sinatra.

A toolkit

By design, Roda has a very small core (450 LOC) providing only the essentials.
All additional features are loaded via plugins that ship with Roda. This is
why Roda is a “web framework toolkit”, using a combination of Roda plugins
you can build your own flavor of the web framework that suits your needs, and
choose exactly the amount of complexity you need.

In my opinion, this is much better than Cuba’s philosophy, where the gem
consists only of a small core, and doesn’t contain any plugins by itself. You
will always need more functionality than the 250 LOC that Cuba gives you, but
it’s not easy to search for external plugins which are scattered all around.
Roda ships with lots of awesome plugins for everyday situations, which are
maintained with the same level of quality as Roda itself, so you’ll rarely need
external ones.

Roda comes with over 60 plugins built in, so I want to show you some highlights.

Like Sinatra, Roda uses the return value of the block as the response body. But
unlike Sinatra, Roda knows what’s been returned in the block, and with the
“json” plugin you can add automatic JSON serialization for those values.

plugin:websocketsroutedo|r|r.get"room"do# Matches if the "/ping" request is a websocket requestr.websocketdo|ws|ws.on(:message){...}ws.on(:close){...}# ...end# If the request is not a websocket request, execution continues and in# that case we render a templateview"room"endend

This plugin ports most of the helper methods defined in Sinatra::Helpers to
Roda, which is awesome if you’re transitioning from Sinatra.

plugin:sinatra_helpers

This will fill your app’s instance methods, which you can then use in the
route block.

# Request methodsredirectbackerror500,"Invalid parameters"not_found"The record was not found"send_file"path/to/file.txt"# Response methodsbody"Winter is coming"status301mime_type:json# And more...

Limitations & Caveats

One downside of using Roda’s routing tree is that, since routes are not stored
in any data structure (because requests are routed dynamically as they come
in), you cannot introspect the routes of the routing tree. In other words, it’s
not possible to implement a rake routes task.

However, you can leave comments above your routes using a special syntax, and use
the roda-route_list plugin/command-line tool to parse those comments and print
the routes.

Another caveat that you should be careful about when using Roda’s routing
tree is that, if you handled the request, you should always explicitly return a
string that will be written as the response body. For example, a POST
/contact request to the following app would return a 404:

This is because Mail.deliver returns an instance of Mail::Message, and since
it isn’t a String, Roda ignores that value and considers the branch unhandled.
The correct thing to do in this case is to return "" at the end of the block.
After a discussion with Jeremy Evans I realized that, because of the dynamic
nature of the routing tree, it’s good that Roda forces you to explicitly state
that you handled the request.

Conclusion

I’m really amazed by Roda’s design, how carefully the framework was thought
through, and the arsenal of its features (I only covered 1/10 of Roda’s
plugins). I love the completely new approach to routing with the routing tree,
I think this power becomes more and more useful as the application grows in
complexity. I use Roda because I found it to be the most advanced framework,
while still having this perfect simplicity that I always wanted.

Janko Marohnić

A passionate Ruby backend developer who fell in love with Roda & Sequel, and told Rails “it’s not me, it’s you”. He enjoys working with JSON APIs and SQL databases, while prioritizing testing, and always tries to find the best library for the job. Creator of Shrine and test.vim.