A few months ago, I went in search of a way to build an extremely lightweight Ruby web app. Merb can be stripped down pretty far, but I wanted a true microframework. Ramaze and Camping were getting close, but didn’t quite fit my taste. Then I discovered Sinatra.
Sinatra apps are typically written in a single file. It starts up and shuts down nearly instantaneously. It doesn’t use much memory and it serves requests very quickly. But, it also offers nearly every major feature you expect from a full web framework: RESTful resources, templating (ERB, Haml/Sass, and Builder), mime types, file streaming, etags, development/production mode, exception rendering. It’s fully testable with your choice of test or spec framework. It’s multithreaded by default, though you can pass an option to wrap actions in a mutex. You can add in a database by requiring ActiveRecord or DataMapper. And it uses Rack, running on Mongrel by default.
One of the most important backend services for Heroku is written using Sinatra. We’re now running several hundred instances of it in our cluster. It’s performed like a champ - I haven’t seen it die or leak memory, other than bugs in our app code.
Some interesting (though not necessarily meaningful) stats.
Lines of framework code (not counting tests or examples)
require ‘sinatra’ pulls in just one file: sinatra.rb. Now that’s a commitment to small.
How about memory footprint? Camping takes the crown here, but Sinatra doesn’t do too shabby:
Memory footprint of an empty application
Aside: I got these numbers using the Linux
free command before and after starting the server. Gauging real memory usage is very difficult because of shared pages, but free is much better than the VSZ/RSZ silliness you see in ps, which don’t tell you very much.
But my real joy in Sinatra is its minimalist simplicity. The direct mapping of URLs to code (routes? who needs ‘em?), the incredible ease of writing tests, and even just the simple fact that the return value from an action is its output. For example, an action might look like:
get '/posts/:id.xml' do Post.find(params[:id]).to_xml end
And a matching test:
should 'get a post in xml format' do Post.expects(:find).with('123') get_it '/posts/123.xml' end
When I return to writing Rails apps after working on Sinatra for a while, I sometimes find myself thinking: “wait, what did I need all this other crap for again?”