thewebdev.de

on software development & the web

building tiny web apps with Sinatra Part 1

10 Jan 2012

Sinatra is an awesome, lightweight and easy to learn little web framework written in Ruby. You may want to use it for projects when Rails is just too much, e.g. for small webservices or small web apps. It will run with many great Ruby webservers, such as Webrick, Mongrel (recommended), thin or Puma (which I prefer).

To get started, you have to install the gem.

[code lang="bash"]
gem install sinatra
[/code]

We will create a little and very basic blog app, and our backlog contains only 3 features: to view the front page that shows us all articles, create new articles and view them. A little disclaimer here: It will be a oldschool web application: no AJAX, no javascript at all. Building RESTful services with Sinatra isn't that complicated and I will show you in a future article how to build a service.

First, we need a file that acts as the controller for Sinatra. You may be used to create multiple controllers classes in their respective file (e.g. in Rails), but Sinatra only needs this single file and we will put all our actions in there. Sinatra merely uses a router inspired pattern. So we only need a new file blog.rb.

[code lang="ruby"]
require 'rubygems'
require 'sinatra'

get '/' do
"Hello World!"
end
[/code]

Now just run this file with

[code lang="bash"]ruby blog.rb[/code]

I prefer to chmod +x it and run it directly but I guess that's just a matter of taste. Now if you point your browser to http://localhost:4567/ you should see Hello World!

This is the simplest action that you can do in Sinatra. As you can see, it has it's own DSL that is really pretty straightforward and easy to use. "get" indicates it matches GET requests. '/' is the route that will be matched and the return value of the block ("Hello World!" will get returned if you are not so familiar with Ruby) will be sent to the browser.

Now wasn't that fast? 5 lines of code to display a hello world and run it on a webserver? That's what I call rapid protoyping ;-) But let's move on writing our blog app.

using Rack

You could of course run your app just as we did using Sinatra with Mongrel. But it's better (and more fun) to run it with rack, a webserver interface for Ruby. It's lightweight and very to get started with. First of all we refactor our existing blog.rb and define a class for our app.

[code lang="ruby"]
class MyBlog < Sinatra::Base
get '/' do
"Hello World!"
end
end
[/code]

Then you need a config.ru file that contains the config for the server:

[code lang="ruby"]
require 'blog'
run MyBlog.new
[/code]

Now I recommend you to use shotgun, a webserver especially for development purposes. gem install shotgun and shotgun config.ru and your server is up and running again. If you want to use a Sinatra application in production, better use Puma instead. It supports a thread pool that you can configure.

1st Feature: show all articles on the front page

So our base system is running and we can go on and finally implement the blog system. I decided to stay lightweight and not use a database at all. If you insist on using a database system, I'll recommend you to use SQLite3 with ActiveRecord but that would just be a bit too much for that tutorial, so we'll use single JSON files. Create a new data folder in the app's root path where we will put our posts in. Then create our first post in a new JSON file data/1.json:

[code lang="json"]
{
"title": "Sinatra is great!",
"body": "Yeah!"
}
[/code]

It's a very basic format and you'll add some more fields but for now that's enough. Now we need to load this file (and hopefully many more to come) to display them on the start page. The convention is to have a lib folder where you have all your library stuff in, so create it in your app's path. I've written a basic class that loads all JSON files and creates an array of Post objects, just save it as lib/post.rb and add a require statement in your blog.rb file.

[code lang="ruby"]
require 'json'

class Post
def self.load_files(directory = 'data/')
posts = []
Dir.glob(directory + '*.json').each do |file|
post = JSON.load(File.read(file))
id = File.basename(file, '.json')

posts << self.new(:id => id, :body => post['body'], :title => post['title'])
end

posts
end

attr_reader :id, :title, :body

def initialize(options)
@id = options[:id]
@body = options[:body]
@title = options[:title]
end
end
[/code]

We just need to run Post.load_files in our little app. Sinatra has it's own method before here, that will be run before each request is handled:

[code lang="ruby"]
require 'lib/post'

class MyBlog < Sinatra::Base
before do
@posts = Post.load_files
end

...
end
[/code]

@posts will now contain an array of Post objects that were loaded from our data folder. Now change the action to render a erb (embedded Ruby) template instead of just plain text.

[code lang="ruby"]
get '/' do
erb :home, :locals => {:posts => @posts}
end
[/code]

What we are doing in here is just calling a Sinatra helper erb and give it our @posts instance variable that will be available in the template as a local variable posts.
Sinatra will now look for a home.erb file in the directory views/, so create it and put the view code in it:

[code lang="ruby"]
<h1>my blog</h1>

<% posts.each do |post| %>
<h2><%= post.title %></h2>
<% end %>
[/code]

This is pretty simple, just a little ruby code embedded in HTML fragments and we only show the post's title.

So that's it for today and I hope you had much fun! There are plenty of other cool things to learn regarding Sinatra and we'll implement the outstanding two features in the next article.