It seems that in Sinatra all route handlers are being written into a single file, if I understand right it acts as a one large/small controller. Is there any way to split it into separate independent files, so when let's say somebody calls "/" - one action is executed, and if something like "/posts/2" is received then another action - similar logic that is applied in PHP?
8 Answers
Here is a basic template for Sinatra apps that I use. (My larger apps have 200+ files broken out like this, not counting vendor'd gems, covering 75-100 explicit routes. Some of these routes are Regexp routes covering an additional 50+ route patterns.) When using Thin, you run an app like this using:
thin -R config.ru start
Edit: I'm now maintaining my own Monk skeleton based on the below called Riblits. To use it to copy my template as the basis for your own projects:
# Before creating your project
monk add riblits git://github.com/Phrogz/riblits.git
# Inside your empty project directory
monk init -s riblits
File Layout:
config.ru app.rb helpers/ init.rb partials.rb models/ init.rb user.rb routes/ init.rb login.rb main.rb views/ layout.haml login.haml main.haml
config.ru
root = ::File.dirname(__FILE__)
require ::File.join( root, 'app' )
run MyApp.new
app.rb
# encoding: utf-8
require 'sinatra'
require 'haml'
class MyApp < Sinatra::Application
enable :sessions
configure :production do
set :haml, { :ugly=>true }
set :clean_trace, true
end
configure :development do
# ...
end
helpers do
include Rack::Utils
alias_method :h, :escape_html
end
end
require_relative 'models/init'
require_relative 'helpers/init'
require_relative 'routes/init'
helpers/init.rb
# encoding: utf-8
require_relative 'partials'
MyApp.helpers PartialPartials
require_relative 'nicebytes'
MyApp.helpers NiceBytes
helpers/partials.rb
# encoding: utf-8
module PartialPartials
def spoof_request(uri,env_modifications={})
call(env.merge("PATH_INFO" => uri).merge(env_modifications)).last.join
end
def partial( page, variables={} )
haml page, {layout:false}, variables
end
end
helpers/nicebytes.rb
# encoding: utf-8
module NiceBytes
K = 2.0**10
M = 2.0**20
G = 2.0**30
T = 2.0**40
def nice_bytes( bytes, max_digits=3 )
value, suffix, precision = case bytes
when 0...K
[ bytes, 'B', 0 ]
else
value, suffix = case bytes
when K...M then [ bytes / K, 'kiB' ]
when M...G then [ bytes / M, 'MiB' ]
when G...T then [ bytes / G, 'GiB' ]
else [ bytes / T, 'TiB' ]
end
used_digits = case value
when 0...10 then 1
when 10...100 then 2
when 100...1000 then 3
else 4
end
leftover_digits = max_digits - used_digits
[ value, suffix, leftover_digits > 0 ? leftover_digits : 0 ]
end
"%.#{precision}f#{suffix}" % value
end
module_function :nice_bytes # Allow NiceBytes.nice_bytes outside of Sinatra
end
models/init.rb
# encoding: utf-8
require 'sequel'
DB = Sequel.postgres 'dbname', user:'bduser', password:'dbpass', host:'localhost'
DB << "SET CLIENT_ENCODING TO 'UTF8';"
require_relative 'users'
models/user.rb
# encoding: utf-8
class User < Sequel::Model
# ...
end
routes/init.rb
# encoding: utf-8
require_relative 'login'
require_relative 'main'
routes/login.rb
# encoding: utf-8
class MyApp < Sinatra::Application
get "/login" do
@title = "Login"
haml :login
end
post "/login" do
# Define your own check_login
if user = check_login
session[ :user ] = user.pk
redirect '/'
else
redirect '/login'
end
end
get "/logout" do
session[:user] = session[:pass] = nil
redirect '/'
end
end
routes/main.rb
# encoding: utf-8
class MyApp < Sinatra::Application
get "/" do
@title = "Welcome to MyApp"
haml :main
end
end
views/layout.haml
!!! XML
!!! 1.1
%html(xmlns="http://www.w3.org/1999/xhtml")
%head
%title= @title
%link(rel="icon" type="image/png" href="/favicon.png")
%meta(http-equiv="X-UA-Compatible" content="IE=8")
%meta(http-equiv="Content-Script-Type" content="text/javascript" )
%meta(http-equiv="Content-Style-Type" content="text/css" )
%meta(http-equiv="Content-Type" content="text/html; charset=utf-8" )
%meta(http-equiv="expires" content="0" )
%meta(name="author" content="MeWho")
%body{id:@action}
%h1= @title
#content= yield

- 296,393
- 112
- 651
- 745
-
12One particularly nice thing about the above structure—specifically putting `require "sequel"` and the `DB` initialization in `models/init.rb`, and using `require_relative` for all files—is that you can cd into your `models` directory, open an IRB console and type `require './init'` and you have your full database and model setup loaded for interactive exploration. – Phrogz Feb 20 '11 at 21:10
-
Is there a generator for such layout? Something like 'my-sinatra-generator new ~/projects/blog'? – Alexander Gromnitsky Mar 18 '11 at 20:39
-
@HenryFlower I don't use one, but it looks like [monk](https://github.com/monkrb/monk) (as suggested by @TK-421) does create some sort of skeleton for you. I tried it out only once and I don't like the default skeleton it uses, but it appears to be easy to [use your own skeleton](http://news.monkrb.com/skeletons). – Phrogz Mar 18 '11 at 20:59
-
27I used a different approach. Code all business logic like users and services in ruby, with no require 'sinatra'. This makes the logic stand on its own. Then I use a single app file to dole out responsibilities to various classes, so about 3 lines of code per route. There are not many routes in the typical application, so my app file is actually not all that long. – Tom Andersen Dec 07 '11 at 16:42
-
@Phrogz What's the benefit of the config.ru file, and launching the app by calling thin, instead of running the app directly (i.e. `thin -R config.ru start` vs. `ruby app.rb`)? – Richard Turner Apr 16 '12 at 13:00
-
@Phrogz Can you, or anyone else, explain what the purpose of NiceBytes is? – bradleygriffith Apr 17 '12 at 23:27
-
@bradleygriffith It's mostly not necessary. It's a) a nice example of including helpers that actually use `module_function`, and b) it happens to be [used by the `minify_resources.rb`](https://github.com/Phrogz/riblits/blob/master/minify_resources.rb#L41) script that's part of the skeleton (not shown in this answer). – Phrogz Apr 18 '12 at 01:09
-
I modified your awesome example to use Datamapper instead, but when I run it with Thin, I'm getting 404 on all the routes. I have triple checked that I include the routes/init file and that init.rb in routes requires both main and login routes, still I get 404 on every route. Any insight? – Phrozen Sep 21 '12 at 01:37
-
@Phrozen Feels like this should be in chat, or a separate question. – Phrogz Sep 21 '12 at 01:45
-
@RichardTurner with this kind of setup you have to use the config.ru method to fire up your App instance that inherits from Sinatra::Application, cause ruby app.rb method won't work. Also this allows you to have multiple class Apps in a single setup and load all in your config.ru - I fixed my issue, I was actually doing this wrong. Thanx – Phrozen Sep 21 '12 at 02:09
-
@Phrogz, Have you tried getting tests working with this? Im having trouble and was wondering if you have any examples out there I might be able to look at. – bradleygriffith Sep 26 '12 at 06:28
-
1Is it a common practise to define a class in multiple files? You are redefining 'MyApp' over and over again in every file. I am new to ruby so it seems weird to me. What's the reason behind this? – 0xSina Jul 08 '13 at 20:42
-
5@0xSina It's not uncommon in Ruby. You don't "define" a class, you "reopen it". For example, the `Array` class is defined by the core library, but you can later "monkeypatch" by using `class Array; def some_awesome_method; end` and a) all previous Array functionality is preserved, and b) all Array instances will get your new code. Classes in Ruby are just objects, and may be augmented and changed at any time. – Phrogz Jul 08 '13 at 22:13
-
1Is `clean_trace` actually a valid option in Sinatra? https://github.com/sinatra/sinatra/issues/148 – briangonzalez Sep 08 '13 at 17:11
-
Any tips on how to manage route versioning and separation of route code by version to avoid complex logic/conditionals in the main routes? – Jan 02 '14 at 07:23
-
There should be a link from Sinatra's [Getting Started](http://www.sinatrarb.com/intro.html) page to this answer. – craig Jan 22 '15 at 16:20
-
is this answer still up to date in 2020? – Marcelo Fonseca Mar 01 '20 at 16:28
-
@MarceloFonseca Yes. – ian Aug 14 '20 at 00:40
Absolutely. To see an example of this I recommend downloading the Monk gem, described here:
https://github.com/monkrb/monk
You can 'gem install' it via rubygems.org. Once you have the gem, generate a sample app using the instructions linked above.
Note that you don't have to use Monk for your actual development unless you want to (in fact I think it may not be current). The point is to see how you can easily structure your app in the MVC style (with separate controller-like route files) if you want to.
It's pretty simple if you look at how Monk handles it, mostly a matter of requiring files in separate directories, something like (you'll have to define root_path):
Dir[root_path("app/**/*.rb")].each do |file|
require file
end

- 10,598
- 3
- 38
- 34
-
7One nice thing about using an explicit `init.rb` versus the above is that you can control the order of loading, in case you have interdependent files. – Phrogz Feb 20 '11 at 21:07
Do a Google search for "Sinatra boilerplate" to get some ideas for how others are laying out their Sinatra applications. From that you can probably find one that suits your needs or simply make your own. It's not too hard to do. As you develop more Sinatra apps, you can add to your boilerplate.
Here's what I made and use for all of my projects:

- 5,845
- 2
- 33
- 57

- 99
- 1
- 2
I know this is an old query but I still can't believe no one mentioned Padrino You can use it as a framework on top of Sinatra, or piecemeal adding only the gems that interest you. It kicks ten buttloads of ass!

- 2,814
- 2
- 24
- 12
The key for modularity on Sinatra for larger projects is learning to use the underlying tools.
SitePoint has a very good tutorial from where you can see modular Sinatra apps and helpers. However you should pay special attention to one important detail. You keep multiple Sinatra apps and mount them with Rackup. Once you know how to write a basic app look at the config.ru file of that tutorial and observe how they mount independent Sinatra apps.
Once you learn to run Sinatra with Rack a whole new world of modularity strategies will open up. This obviously invites to try something really useful: now you can rely on having individual Gems for each sub application, what might enable you to easily version your modules.
Do not underestimate the power of using gem-modules for your app. You can easily test experimental changes in a well delimited environment and easily deploy them. Equally easy to revert back if something goes wrong.
There are a thousand ways to organize your code, so it would not hurt trying to get a layout similar to Rails. However there are also some great posts about how to customize your own structure. That post covers other frequent needs of most web developers.
If you have the time, I encourage you to learn more about Rack, the common ground for any Ruby based web application. It might have a far lesser impact in how you do your work, but there are always certain tasks that most people do on their apps that fits better as a Rack middleware.

- 16,555
- 7
- 56
- 102
My approach to host different projects on the same site is to use sinatra/namespace
in such way:
server.rb
require "sinatra"
require "sinatra/namespace"
if [ENV["LOGNAME"], ENV["USER"]] == [nil, "naki"]
require "sinatra/reloader"
register Sinatra::Reloader
set :port, 8719
else
set :environment, :production
end
for server in Dir.glob "server_*.rb"
require_relative server
end
get "/" do
"this route is useless"
end
server_someproject.rb
module SomeProject
def self.foo bar
...
end
...
end
namespace "/someproject" do
set :views, settings.root
get "" do
redirect request.env["REQUEST_PATH"] + "/"
end
get "/" do
haml :view_someproject
end
post "/foo" do
...
SomeProject.foo ...
end
end
view_someproject.haml
!!!
%html
...
Another detail about subprojects I used was to add their names, description and routes to some kind of global variable, that is used by "/"
to make a guide homepage, but I don't have a snippet right now.

- 34,866
- 14
- 107
- 142
When Monk didn't work for me, I started working on templates myself.
If you think about it, there is nothing special about tying up a set of files. The monk philosophy was explained to me early in 2011 during RedDotRubyConf and they have specifically told me that it's really optional to use it especially now that it's hardly maintained.
This is a good start for those who want to use ActiveRecord:
Simple Sinatra MVC

- 2,099
- 4
- 25
- 34
Reading the docs here:
It appears that Sinatra allows you to decompose your application into Ruby Modules, which can be pulled in through the Sinatra "register" method or "helpers" methods, like so:
helpers.rb
require 'sinatra/base'
module Sinatra
module Sample
module Helpers
def require_logged_in()
redirect('/login') unless session[:authenticated]
end
end
end
end
routing/foos.rb
require 'sinatra/base'
module Sinatra
module Sample
module Routing
module Foos
def self.registered(app)
app.get '/foos/:id' do
# invoke a helper
require_logged_in
# load a foo, or whatever
erb :foos_view, :locals => { :foo => some_loaded_foo }
end
end
end
end
end
end
app.rb
#!/usr/bin/env ruby
require 'sinatra'
require_relative 'routing/foos'
class SampleApp < Sinatra::Base
helpers Sinatra::Sample::Helpers
register Sinatra::Sample::Routing::Foos
end

- 138
- 1
- 4