It's important to remember that Sinatra is an amazingly flexible micro framework with a top level DSL and not a feature packed framework like Rails. It's purposefully built to have just enough functionality to give you what you need to get going. There are incredibly useful helpers and extensions available that can be found with a bit of Googling, not to mention the thousands of Ruby Gems available to use.

Sinatra literally gives you a blank slate and all the application design decisions are entirely up to you. I found it much easier to work with Sinatra once I completely let go on any preconceptions I had about how web applications should be structured.

The simplicity of Sinatra is why it's so powerful - it fits into your brain and it's up to you to implement the methodologies, approaches and application structures that best suite your project. Generally, you would use components that you already know instead of learning a whole new framework.

It all comes down to good project organization and sufficient upfront planning,
I would recommend reading up a bit more on MVC, HMVC or other software architectural patterns to get a good understanding of how it can solve common problems you might encounter during development.

Sinatra's lack of opinions is one of it's greatest benefits. It won't stop you from writing unmaintainable code however - that's your responsibility.

There is no 'one size fits all' approach to building Sinatra applications - you have the freedom to experiment and work the way you need to.

We'll be working through organizing your Sinatra project using MVC in a later chapter.

First, let's start with talking about the difference between classic and modular style applications in Sinatra.

Classic & Modular Style Applications

There primarily two different ways to use Sinatra, both have slightly different use cases and requirements:

Let's take a look:

Classic Style is generally used more when creating smaller micro-sites or apps. I use the word 'generally' loosely here. These apps usually run from a single application file:

app.rb:

require 'sinatra'

get '/' do
  erb :"homepage"
end

get '/products' do
  @products = [
    {:name => "Apple Macbook pro", :price => 1200, :sku => "mbp0001"},
    {:name => "HP P1102w Printer", :price => 389, :sku => "hp0001"}
  ]
  erb :"products"
end

post '/product/purchase/:sku' do
  # Start checkout/purchase routine
end

Terminal:

ruby app.rb

Some developers don't like the classic style applications because it pollutes the global namespace in Ruby, but it's subjective and if the classic approach is the perfect tool for problem you are attempting to solve, then do so.

When you use require 'sinatra', it automatically sets up the application with a few default settings for you - think 'plug and play'.

Modular Style is simply a way to write your app as independent modules that can run within the same parent application. This is great if you plan to use more advanced structuring of your application files - we'll talk more about this in a later.

Important: If you are planning to package your app as a gem or extension, you will need to use the modular approach.

Unlike in classic mode, some options will not be automatically configured for you when you create your apps using the modular approach.

This approach uses require 'sinatra/base' instead of require 'sinatra'.

Here's an example:

app.rb:

require 'sinatra/base'

class MyStore < Sinatra::Base
    set :logging, true
    set :sessions, true
    set :dump_errors, false

    get '/' do
        locals = {
         :title => "My Awesome Store"
        }
        erb :"homepage", {:layout => false}, locals
    end
end

api.rb:

require 'sinatra/base'
require 'sinatra/contrib'
require 'net/http'
require 'json'

class MyAPI < Sinatra::Base
    set :logging, true
    set :sessions, false
    set :dump_errors, false

    helpers Sinatra::JSON

    get '/weather.json' do
      @uri = URI('http://api.openweathermap.org/data/2.5/weather?q=London,uk&APPID=<YOUR API KEY HERE>')
      @resp = JSON.parse(Net::HTTP.get(@uri))

      json({
        :name => @resp["name"],
        :weather => @resp["weather"]
      })
    end
end

config.ru:

require './app'
require './api'

map "/" do
    run MyStore
end

map "/api" do
    run MyAPI
end

Terminal:

bundle exec rackup

In the modular style example above, we have two of our own classes extending the Sinatra::Base class, each inheriting all functionality from the Base class and handling requests in their own context and scope. The MyStore application will be available to any requests on the root url '/' and the MyAPI app will respond to requests on the '/api' url.

You'll also notice that we're requiring sinatra/contrib in api.rb - it's a project that closely follows Sinatra's release cycle and contains a collection of commonly used extensions, you will undoubtedly find some of them useful.

We've used the built-in Sinatra.helpers() method to register the Sinatra::JSON helper inside our MyAPI class. This makes the json({:key => val}) method available to us. The json() method automatically sets the output content-type to application/JSON for us before sending the response back to the browser.

You will usually find larger applications built using the modular approach because you can completely decouple the various components of your app - something that's very useful in multi-faceted systems with dashboards, API's etc.

Install Sinatra::Contrib using:

gem install sinatra-contrib

More information: