If you’re a web designer or developer, you’re well acquainted with prototyping. From raw wireframing to creating interfaces in Photoshop, designers map out how sites will work before they create them.
This article introduces Sinatra, a so-called “micro” web framework that helps you create real (albeit simple) web apps extremely fast, allowing you to prototype flows and behaviors that you may want to integrate into a final product. Sinatra is written in Ruby, but for our purposes we’ll use it as the “glue” between our HTML/CSS and the domain-specific Sinatra functions, so you won’t have to know much more than a few simple methods to get to “Hello world.” In this article, our example will be an extremely simple Twitter app that accepts two usernames and tells you if one user is following the other.
Blurring “front end” and “back end” and the threshhold of “appness”#section1
Although the word “app” gets thrown around a lot today, I’d set the boundary between website and web app at the point where an individual user’s contribution determines the course of events on screen. At first, apps may seem easy to prototype as successions of screens; but when you actually have a running app, unpredictable situations always reveal themselves. There might, for example, be different homepages for logged in and new users. A user may have to jump over to a third party site for authentication before returning. How will you make that as seamless an experience as possible? A must-have feature may pop out at you when you (as your first user) get frustrated trying to do something. Starting to play around with these actions in Sinatra allows you to explore these problems almost immediately, collapsing the gap between back end and front end. If your app draws from a third party API rather than a database, the “back end” becomes essentially boilerplate, and you can actually use real data in your mockup instead of hard coding (where the temptation to create best case scenarios always lingers).
Sinatra apps make great proofs of concept and Minimum Viable Products, and sometimes they can even flower into full-blown applications. They’re also great for spinning something up extremely quickly during breaking news or viral situations. I frequently spend a few days on a quick Sinatra app to see if something could be done with a certain technology or concept before committing to it. In a word, Sinatra is a kind of web designer’s swiss army knife. It doesn’t take long to learn, it comes in handy for prototyping bigger projects, and it’s also great when you need to get a web app running quickly without dwelling much on the back end.
For this tutorial, you’ll need to have Ruby installed. If you’re on a Mac, here’s a great guide from Dan Benjamin on setting up Ruby and RubyGems for Snow Leopard. On Windows, check out RubyInstaller, and on Ubuntu Linux run
sudo apt-get install ruby-full. You’ll also have to install RubyGems, the Ruby package manager. RubyGems distributes Sinatra and the Twitter gem (a library that allows us to work with the Twitter API). We’ll also need to use the command line a bit, but just to install the libraries, and to start and stop our app, so don’t worry if you haven’t used the terminal much (though, if you want a crash course, I highly recommend PeepCode’s Meet the Command Line screencast). Just know where it is—on Mac OS X, it’s Terminal.app in the Utilities folder within Applications. For the purposes of this article, I’ll denote the command line prompt with
prompt>. Just type the command after the prompt and hit return. Anything shown under a
prompt> is the output of that command in the terminal.
Once you have Ruby and RubyGems installed, you’ll need to open up the Terminal and run:
prompt> sudo gem install sinatra twitter
Now we have all the libraries we’ll need. Once that’s done, you can close the Terminal.
Organizing a Sinatra app#section3
Now we’re ready to jump into writing our app. But first, a little about how Sinatra works. The framework is based on the MVC pattern, where a controller mediates between a “model” or a representation of the data you’re working with, and the view, which is what that data looks like on the front end. A model is usually a database, but can really be any source of data, such as an API or user input stored in a cookie. The important thing, though, is that it is not coupled to its representation on screen (or, in MVC lingo, the “view”). A controller talks to both the model and the view to determine what data gets routed where.
In the context of prototyping, making a standard HTML/CSS mockup would be just the V in the MVC, with the data hardcoded in for demo purposes. Starting to think about how the model and controller affect the view is getting to the heart of what makes your new thing an “app.” Your controller can take cues from both sides and determine how the data is represented in the view or updated in the model.
Since Sinatra is a “micro-framework,” it really only provides the “C” and “V” in MVC. It’s agnostic about how you bring in your model. More fully-featured web frameworks like Ruby on Rails provide all three, with a heavy database layer as well. Since we’re going to be grabbing all our data from the Twitter API, our model will be the data it returns—we’ll just need a few methods to request the data we want and send it to our controller.
How does a controller work? In Sinatra, it’s simply a list of HTTP verbs with instructions on what to do in response to those verbs. When your browser issues a
GET command to a Sinatra app with a certain URL pattern, it will do whatever is within the block that matches the pattern.
Here’s an example of the world’s simplest Sinatra app.
At the top of every app file, we’ll put the following two lines to let Ruby know it’s a Sinatra app:
require 'rubygems' require 'sinatra'
Then, our simple app:
get '/hello' do "Hello" end
Everything between the
end are what Sinatra will execute when the browser asks for a URL. In this case if a browser navigates to /hello, it will display the text “Hello” in the browser. Here’s a slightly more interesting example:
get '/hello' do user = params[:user] "Hello " + user + "!" end
In this example, we’re capturing GET query parameters to be passed into our app. If you navigate to /hello?user=Al in this app, the page will display “Hello Al!”
Alternatively, for prettier URLs, the example can be written like so:
get '/hello/:user' do user = params[:user] "Hello " + user + "!" end
Then, navigating to /hello/Al will give you the same result. To run this app, change to its directory in the Terminal (you might name the app “myapp.rb” for now), and run:
prompt> ruby myapp.rb == Sinatra/1.0 has taken the stage on 4567 for development with backup from Thin >> Thin web server (v1.2.7 codename No Hup) >> Maximum connections set to 1024 >> Listening on 0.0.0.0:4567, CTRL+C to stop
Now that the local server is running, try it out by pointing your browser to to http://localhost:4567/hello/Al. To stop the server, hit control-C (you’ll need to stop and restart the server if you make any changes to the code).
What about the View?#section4
This is all about adding a layer of functionality to a static mockup, so how do we interpolate this stuff into actual HTML? We use views. Sinatra makes this really simple by looking for files in a views folder inside your app’s root. In each verb block, you point to a specific view. Here’s the same example with a view:
get '/hello' do @user = params[:user] erb :hello end
Now if your app is located in /myapp/myapp.rb in your file system, you’ll need to put the “hello” view in /myapp/views/hello.erb. Here’s what that file looks like:
There’s a lot going on here. The first thing you might notice is that we put an
@ before our user variable. This turns it into an instance variable. Instance variables are important in Ruby, but for our purposes, it’s just good to know that only instance variables can be passed to views. If you have some data within a verb block that you want to show up in a view, store it in an instance variable or it won’t make that jump.
To call our view, we’re using the
erb method. ERB stands for embedded ruby—it’s the templating language we’re using to interpolate Ruby into our HTML. By putting
erb :hello as the last line in our verb block, we’re telling Sinatra we want to use ERB to render a template called “hello.erb” inside our app’s views folder. When we do that, it will know to pass all the instance variables present in our block into that view.
Working with real data, the Twitter API#section5
As you can see, Sinatra makes it trivially easy to create a web app, if the threshhold for “appness” is that it can manipulate user input. To make it a real app, we’ll need some data to work with. For that, we’ll pull out the Twitter API via the Twitter gem. After this part of the tutorial, we’ll have a super bare bones Twitter app that you can use to drape a beautiful HTML/CSS mockup over.
Let’s make a new folder called followapp, and a file inside it called followapp.rb. In this file, we’ll use the same require statements we had before, but will add one more, to include the Twitter library:
require 'rubygems' require 'twitter' require 'sinatra'
In this app, we’ll have two “actions”: an index page which will collect the users we want to check follow status on, and a results page, which will display if user A is following user B.
Here’s what our index action looks like:
get '/' do erb :index end
Because we aren’t doing anything in the back end, all we have to do is tell Sinatra to point to a certain view. In that view we’ll have a form that collects the user data and submits it to our other action. Here’s what the /views/index.erb file looks like:
<h1>Enter two users</h1> <form method="get"> Does <input type="text" name="user1" /> follow <input type="text" name="user2" />? <input type="submit" value="Go" /> </form>
As you might expect, this submits via GET, creating a URL that looks like /follows?user1=username&user2=username. To handle that, we just create another action in our controller that responds to those parameters:
get '/follows' do @user1 = params[:user1] @user2 = params[:user2] #not implemented yet @following = is_following?(@user1, @user2) erb :follows end
This action will handle the response. It grabs the two parameters we’re giving it from the index page and generates a variable called
@following which we haven’t implemented yet.
I’ll follow you if you follow me#section6
Here’s how we implement the core engine of the app—the
is_following? method, which will find out if user one is following user two. Twitter doesn’t make it easy to check exactly if one user is following another, but they do have a followers endpoint in their API which will return a list of user IDs for all the followers on a certain account. The one wrinkle to using this is that we need to get a user ID from a screen name to check it against that list. For that, we’ll just use the users/show endpoint. It’s simple with the Twitter gem. In our app, here’s the method we’ll write:
def twitter_id(screen_name) Twitter.user(screen_name).id end
That takes a username and returns the user ID. So if I were to run
twitter_id(“a_l”), I would get 7865282. Now we just need one more method,
is_following? to compare two IDs. Here’s what that looks like:
def is_following?(a,b) followers = Twitter.follower_ids(twitter_id(b)).ids followers.include?(twitter_id(a)) end
Twitter.follower_ids method returns an array of user IDs with user B’s followers. We then use
include? to check if user A’s user ID is present in the array. (For more on how
include? and arrays work, see the Ruby Array documentation.) So, with just a few lines of code, we now have the kernel of our app. (Note, the
Twitter.follower_ids method only returns that user’s first 5,000 followers. There are ways to get all of them, but it would complicate the app.) That brings us back to the line in our action:
@following = is_following?(@user1, @user2)
This instance variable will then answer
false as to whether
@user1 is following
@user2. What will our view look like? (This is contained in /followapp/views/follows.erb.)
Whew, that’s it! This view will output “Username follows Username” or “Username does not follow Username.”
The results of running the new Twitter app.
Here’s the entire “app”:
require 'rubygems' require 'twitter' require 'sinatra'def twitter_id(screen_name) Twitter.user(screen_name).id enddef is_following?(a,b) followers = Twitter.follower_ids(twitter_id(b)).ids followers.include?(twitter_id(a)) endget '/' do erb :index endget '/follows' do @user1 = params[:user1] @user2 = params[:user2] @following = is_following?(@user1, @user2) erb :follows end
Note: the full code for this app is available on Github.
In about 20 lines of code we have a working web app that can act as a prototype for a project we’re working on. Since Sinatra makes it so easy to build these micro apps, we can now prototype functionality the way we used to prototype interface decisions. Seeing functionality in action also makes it much easier to decide if this is a project worth pursuing. Everything looks glossier in Photoshop comps or even perfect HTML, but when you use an app, you can really tell if this is the right road to be going down. With Sinatra those decisions can come sooner rather than later when it’s much more painful to change course.
Show and tell#section7
Once you have an app up and running, you’ll probably want to share it. Remember how Sinatra starts up by default at http://localhost:4567? Sharing your app within a local network is as easy as replacing localhost with your internal IP address. Usually this starts with 10.0 or 192.168 and can be found in your network preferences. So, to pass around your app within your office or VPN, just use http://localip:4567.
Sharing your app with the world over the Internet is a little trickier, but not much. The absolute easiest way to launch (“deploy”) your app is with a service like Heroku or Engine Yard, which are web hosts designed specifically to run Ruby (Rails and Sinatra) apps and can get you up and running very quickly with little effort. Heroku is free for small apps, making it ideal to share prototypes, and it has a much simpler interface. Engine Yard has more options, but is a little tricker and also has no free plan. Unfortunately, most cheap shared hosting outside these specialty services are not set up to host Ruby apps, but your mileage may vary.
Whether it’s a prototype, a single serving site, or something more complex, it’s always a thrill to see even the simplest app up on the web. Adding that “appness” brings life to what were previously just pages on a server.
There’s much more to Sinatra than I’ve covered here. To learn more, check out some of these resources: