Roda is a Ruby web framework created to bring the best of Sinatra and Cuba into Ruby web development. Roda was designed around the concept of a “routing tree.”
In web development, routing is the process of taking a request, then finding the code that would handle that request. Consider an HTTP request path like the one below.
GET users/1/invoices
When deciding how to handle a route, a typical web framework (e.g., Ruby on Rails or Sinatra) looks at the full request path, iterating an array of possible routes to match the request.
On the other hand, a routing tree framework such as Roda checks each segment of the request path before continuing.
In handling the request above, Roda looks at the first segment, /users. If this segment does not match the request, it skips the entire /users branch so that the other routes under /users are not considered. If /users matches, it is going to look into the /users branch for /1. It continues this way until the route is found, or otherwise returns a 404(not found) status if the route cannot be matched.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Since route handling does not involve iterating through an array of routes, per-request overhead is significantly lower when compared to other Ruby frameworks.

What makes a routing tree interesting is not just performance, but that at any point during routing, you can operate on the current request.
Roda is designed to be simple, both internally and externally. It uses a routing tree to enable you to write simpler and DRYer code.
Roda has very little global namespace pollution. All instance variables used internally in the routing tree are prefixed with an underscore (@_variable), so they do not conflict with the instance variables the user wants to use. Additionally, Roda prefixes constants with Roda:: (Roda::constant) to avoid conflicting with the constants that you use for your application.
Roda is extensible via broad plugin support. You can override any part of Roda and call super to get the default behavior.
Let’s start off by creating a very small web application to understand what working with Roda looks like. Roda is more of a Ruby library than a framework, and does not have CLI utility commands for performing regular tasks. So unlike Rails and other frameworks, it does not have commands for creating a new project, generating models, controllers, and other command-repetitive tasks.
Let’s create a directory to hold our project files.
mkdir my_roda_app
Then we add a Gemfile for managing the gems we will use. Add the roda gem into the file, and then puma, which will be our web application server. Our Gemfile will look like this.
source "https://rubygems.org"
gem "roda"
gem "puma"
Now we run bundle install to install the gems we’ve added.
bundle install
Most Ruby web frameworks were built on top of Rack, which, according to Rack’s documentation, is a modular Ruby web server interface.
Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.
Rack documentation
Roda is Rack-compatible, so we start out by creating a rackup file using the standard filename config.ru. In it we require roda, and then create a new class to represent our application. This app will inherit from the Roda class.
Because Roda is built on top of Rack, every class that inherits from Roda implicitly becomes a Rack application. In order to tell Rack (and the web server) to execute our app for HTTP requests, we have to tell it to run the App class.
require "roda"
class App < Roda
route do |r|
"Hello World!"
end
end
run App
Then from the command line, we run the rackup command to start up the web server and begin serving requests.
rackup
Open the browser and navigate to http://127.0.0.1:9292 to see “Hello world!”

Our app at its current state returns “Hello world!” as the response for every request route. Let’s introduce Roda’s routing method to make the app return the same response, but for only a specific request path and method.
r.on methodThe first routing tree method is r.on, which creates branches in the routing tree. Let’s add r.on to our example app.
require "roda"
class App < Roda
route do |r|
r.on 'users' do
"Hello Users!"
end
end
end
run App
We called r.on with the string users, which will match the current request path if the the request path starts with users.
The Roda.route method is the start of the routing tree. All requests coming into our application will be yielded into the block passed into Roda.route. The block is yielded a route request instance with some additional methods. Conventionally, the block argument is named r (e.g., |r|). The additional methods (e.g., r.on ) passed to the route request instance are used for routing the request.
Route methods accept arguments called matchers, which are used to match the current request. In our example above, we passed a string matcher users as an argument to r.on to create the users branch.
In Roda, string matchers usually match the first segment of the request path. So if the request path starts with users, it matches and is redirected to the passed-in block, which returns the “Hello Users!” string that Roda is using as the response body.
If the request path starts with profile (which does not match), r.on returns nil without yielding to the block, and execution would continue after the call. If no other block exists, which is the case in our example above, Roda.route will return nil and Roda will return a 404 status code with an empty response body.
r.is methodr.on will match all request paths starting with users, including users/non-existent-user. Usually, this is not what we want. Instead, we want to return a 404 response for any path that has not been specifically handled.
This is where r.is comes in handy. It only matches a path if all of the arguments match and there are no further entries in the path after matching. In other words, it finalizes the routing path.
r.is will only match users and not users/non-existent-user. Routing in Roda is done using a combination of the r.on and r.is methods. r.on does prefix matching of the request path, and r.is does full matching of the request path. So while r.on creates branches, r.is creates leaf nodes.
Let’s add a r.on to our example.
require "roda"
class App < Roda
route do |r|
r.on 'users' do
r.is "list" do
"Hello Users!"
end
end
end
end
run App
Our app will match any request with the path /users/list. r.on creates a branch that handles all paths under users, and r.is creates a node that matches only if the current path is list. r.is is able to match list because the request path is modified as the request is being routed.
When the request for users/list comes in, the routing tree uses the initial request path users. When the r.on "users" request method matches, it consumes users from the front of the request path. Inside the r.on block, the request path is empty.
In the next r.is block, the current path is list and will only match if all of its matchers agree throughout the request path. So in this example case, the request will be matched and “Hello Users!” will be returned.
r.getRouting will usually take the request method into account as well. While r.on and r.is focus on routing using the request path, r.get is for routing based on the GET request method. If it is invoked without a matcher, it puts a simple match against the request method. If invoked with a matcher, a terminal match is performed against the request path.
require "roda"
class App < Roda
route do |r|
r.on 'users' do
r.is "list" do
r.get do
"Hello Users!"
end
end
end
end
end
run App
A GET users/list request would return “Hello Users!”
r.postWhile r.get matches GET requests, r.post matches POST requests.
require "roda"
class App < Roda
route do |r|
r.on 'users' do
r.is "list" do
r.get do
"Hello Users!"
end
r.post do
"User added"
end
end
end
end
end
run App
A POST users/list request would return “User added.” r.get and r.post are used to handle various request methods on similar request paths.
Roda has the ability to execute code during the routing process. This makes it easy to write simpler and DRYer code and avoid code duplication.
If you want to check whether someone is logged in before accessing a route, you can put the code that checks for login as the first line in the Roda.route block. This is useful when dealing with separate request methods for the same request path. For instance, a GET request will find a user and return their details, and a POST request will find a user and update their details.
We can abstract the logic for finding the user to a routing block shared by both request methods as shown below.
class App < Roda
route do |r|
require_login!
r.on 'users' do
r.is ":id" do |user_id|
@user = User.find(user_id)
r.get do
@user
end
r.post do
@user.update(params[:user])
end
end
end
end
end
run App
Roda is designed to be lightweight and ships only with essential features to get your app quickly running. All additional features are loaded via plugins; Roda ships with over 60 built-in plugins, all of which are maintained with the same level of quality as Roda itself, so you’ll rarely need external ones.
Jeremy Evans on Github
RubyConf 2014 – Roda: The Routing Tree Web Framework by Jeremy Evans
Mastering Roda
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

line-clamp to trim lines of textMaster the CSS line-clamp property. Learn how to truncate text lines, ensure cross-browser compatibility, and avoid hidden UX pitfalls when designing modern web layouts.

Discover seven custom React Hooks that will simplify your web development process and make you a faster, better, more efficient developer.

Promise.all still relevant in 2025?In 2025, async JavaScript looks very different. With tools like Promise.any, Promise.allSettled, and Array.fromAsync, many developers wonder if Promise.all is still worth it. The short answer is yes — but only if you know when and why to use it.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 29th issue.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now