Building a Sinatra app in Ruby

Sinatra is a Ruby web framework that lets you build apps with less code. It has an expressive syntax and is less complex than other frameworks like Rails. In this article, we’ll use Sinatra to make a URL shortening app and learn how to use this powerful tool.

Sinatra is a lightweight web application framework written in Ruby. It provides a simple and easy-to-use syntax for building web applications. The framework focuses on being minimalistic, allowing developers to quickly create web applications without having to deal with a lot of the boilerplate code and relatively rigid way of doing things that accompany larger and more popular frameworks like Rails.

In this article, we'll use the Sinatra framework to build a simple URL-shortening app where a user can input a long URL and generate a short URL that redirects to the original URL when clicked. The app will also have a feature for keeping track of how many times a shortened URL is clicked.

Takeaways

By following this tutorial, you'll learn the following:

  • What Sinatra is and why you should use it
  • How to leverage its powerful routing features
  • How to work with a database
  • How to Add views.

Pre-requisites

To follow along with this tutorial, it's necessary to have the following:

  • Ruby installed on your development environment. Head here in case you don't have it installed and need a step-by-step guide. We'll be using Ruby 3.2.2 for this tutorial.
  • A Sinatra installation. If you don't have Sinatra installed, don't worry; we'll cover this in this tutorial.
  • Some basic experience with using Ruby.

Now, let's get started by understanding why we need Sinatra in the first place.

Why Sinatra?

With such full-featured Ruby frameworks as Ruby on Rails around, you might be wondering why anything else would be needed for your app development needs. Although Rails can handle almost anything you could throw at it, there are instances where having something else makes a lot of sense. Specifically, Sinatra is a great choice for app development because:

  • It's lightweight: Sinatra has very minimal dependencies, which makes it very lightweight compared to something like Rails. This makes it an easy framework to wrap your head around and allows you to have more control over how you architect your app’s structure.

  • It has powerful routing features: Sinatra’s routing system is flexible and expressive, allowing you to easily define handlers for common HTTP verbs, such as GET, POST, PUT, and DELETE. With Sinatra, you can also define custom and dynamic routes using route patterns, named parameters, and route segments, which can help you build powerful web applications, especially microservices and RESTful APIs.

  • It's modular: Sinatra is designed to be very simple and minimalistic. However, the framework follows a modular design, which allows you to pick and choose the components that meet your specific needs. If you need features like authentication and session management, Sinatra supports middleware that will take care of these and more. This allows for extreme developer flexibility that a more popular framework like Ruby on Rails might not give you.

  • It leverages the extensive Ruby ecosystem: As a Ruby framework, Sinatra seamlessly integrates with Ruby’s large and vibrant ecosystem. You can leverage the extensive Ruby gem library to obtain gems for almost anything you can think of instead of building from scratch. When you hit a roadblock in your app development journey, the Ruby community will be there to help.

Of course, there's more to Sinatra than the features we've highlighted here, but these will suffice for now. Next, let's go over the app we'll be building using the framework.

An overview of the app we'll be building

Our goal is to build a simple URL-shortening app using Sinatra. However, it makes a lot of sense to have an overview of the app's functionality since this will guide us on the build process. First, we should understand how URL shortening works, which we will cover next.

How URL shortening works

Generally speaking, URL shorteners work as outlined below:

  • The user enters a long URL, such as 'https://www.this-is-a-long-url-example.com'.
  • The app generates a random string, which is appended to a base URL and then associated with the long URL entered by the user, such as 'https://localhost/hn78JH'.
  • Then, both the long URL and it's shortened counterpart are saved to the app's database.
  • When a website visitor clicks the shortened URL, they are redirected to the original long URL. An optional visits counter can be attached to this flow so that it's incremented automatically with each visit.

Now that we know how the URL-shortening process works, let's kickstart our build.

Quick note: The full app code repo is available here.

Building a URL shortener using Sinatra

We will progressively build our URL-shortening app following the outline we discussed in the previous section on how URL shortening works.

Let's begin by installing the Sinatra gem, but first, it's important that we explain a very important concept that will determine how we build the app: modular vs. classical Sinatra apps.

Modular vs. classical app structure

When it comes to building Sinatra apps, you can go one of two ways: classical or modular. Without going into too many details, classical apps are structured in such a way that all app functionality lives within a single file. Modular apps feature different levels of abstraction, but more importantly, the main application (or applications since they can be more than one) is sub-classed from Sinatra::Base. You can read more on this here. While it is possible to build sophisticated apps using the classical approach, we'll go the modular route for the URL-shortener app.

Installing Sinatra and initializing the project

Installing Sinatra is as simple as running the command gem install sinatra. Then, create a new directory in an appropriate local folder to hold the project files:

mkdir sinatra_url_shortener

Now let's cd into the newly created app directory and create a basic starter file:

cd sinatra_url_shortener && touch app.rb

Next, open the project in your favorite editor and modify app.rb as shown below:

# app.rb

require 'sinatra'

get '/' do
  'Sinatra rocks!'
end

The code above is a very simple Sinatra app composed of a single file with a require statement to include Sinatra and a simple route definition telling the app to respond with the text "Sinatra rocks!" when someone hits the home or root page.

Assuming you're still in the project's root, run the script with the ruby app.rb command and then visit http://127.0.0.1:4567/ to see a Sinatra app running:

Basic Sinatra app running

Of course, this is a far cry from the vision we have for our app, but it's a great start. Next, let's continue the build by adding a Gemfile and some configuration files.

Adding a Gemfile, Rack configuration and Rakefile

Since our goal is to build a modular Sinatra app with proper abstractions in place, let's continue the build by adding a Gemfile to hold the app's gem dependencies in a separate, dedicated file. We'll also need a Rack configuration file, config.ru, which will define how the app will be run:

# Gemfile

source 'https://rubygems.org'

gem 'dotenv'
gem 'json'
gem 'pry'
gem 'rake'
gem 'sassc'
gem 'sinatra'
gem 'sinatra-activerecord'
gem 'sinatra-contrib'
gem 'sinatra-static-assets'

group :development do
  gem 'foreman'
  gem 'rubocop'
  gem 'sqlite3', '~> 1.3', '>= 1.3.11'
end

group :test do
  gem 'rack-test'
  gem 'rspec', require: 'spec'
end

group :production do
  gem 'pg'
end

We won't go into the details of what each gem does, but we’ll highlight a few important ones:

  • sinatra - because we are building a Sinatra application.
  • sinatra-activerecord - since we'll be using Active Record as the ORM wrapper around the app's database.
  • sinatra-contrib - which will handle automatic app reloading in development since Sinatra does not come with this functionality by default.
  • rake - to help us run app-specific tasks, such as rake db:create in the command line.
  • rack-test and rspec - for testing purposes.
  • sassc - to use SCSS stylesheets and compile them into CSS (We'll use this later to style our app a bit).
  • json - useful for working with the JSON data format, including returning JSON data via API endpoints.

Next, add a Rakefile in the app's root and edit it as follows:

# Rakefile

require 'sinatra'
require './app'
...

run App

Finally, for this section, we'll need to modify the app.rb file as shown below:

# app.rb
require 'sinatra'
require 'sinatra/reloader'
require 'sinatra/activerecord'
require './helpers'
require 'securerandom'

class App < Sinatra::Base
  configure do
    set :json_encoder, :to_json
    set :erb, layout: :layout
  end

  before do
    headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Headers'] = 'accept, authorization, origin'
  end

  options '*' do
    response.headers['Allow'] = 'HEAD,GET,PUT,DELETE,OPTIONS,POST'
    response.headers['Access-Control-Allow-Headers'] =
      'X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept'
  end

  get '/' do
    erb :index
  end
end

In brief, here's what we've just defined:

  • configure block - We set the JSON encoder and a layout file that will be used by the app's views (similar to the application.html.erb layout file in a Rails app).
  • before and options blocks - These are generally called filter blocks, and we use them here to define some headers.
  • Finally, we define a root route that uses a view file, index.erb, which we'll get into a bit later.

Next, let's work on the app's database and the Link model.

Adding a database connection and Active Record model

In development, using SQLite gives us an easy-to-use file-based database that will suffice for our simple URL-shortening service.

To define the database connection, first create a folder called config in the app's root. Then, inside it, create two files: application.rb and database.yml.

Let's edit application.rb as shown below:

# config/application.rb

require 'active_record'
require 'sqlite3'
require 'yaml'

config_path = File.join(__dir__, 'database.yml')
ActiveRecord::Base.configurations = YAML.load_file(config_path)
ActiveRecord::Base.establish_connection(:development)

# Loads all models
Dir["#{__dir__}/../models/*.rb"].each { |file| require file }

Then the database.yml:

# config/database.yml

development:
  adapter: sqlite3
  database: db/app.sqlite3

production:
  adapter: postgresql
  encoding: unicode
  pool: 5
  host: <%= ENV['DATABASE_HOST'] %>
  database: <%= ENV['DATABASE_NAME'] %>
  username: <%= ENV['DATABASE_USER'] %>
  password: <%= ENV['DATABASE_PASSWORD'] %>

To finish this section, run bundle exec rake db:create to create an SQLite database under the db folder in the app's root.

Next, let's work on the URL model.

Let's start with the migration to create the short_links table. In the terminal in the app's root, run the following command:

bundle exec rake db:create_migration NAME=create_short_links

This will create a migration file under db/migrate, which we need to edit as follows:

# db/migrate/XXXXX_create_short_links.rb

class CreateShortLinks < ActiveRecord::Migration[7.0]
  def change
    create_table :short_links do |t|
      t.string :short_url
      t.string :long_url
      t.integer :visits, default: 0

      t.timestamps
    end
    add_index :short_links, %i[short_url long_url]
  end
end

Then, run bundle exec rake db:migrate, which will create the short_links table in the database. Let's now turn our attention to the model.

Create a new file, models/short_link.rb, and edit it accordingly:

# models/short_link.rb

# frozen_string_literal: true
class ShortLink < ActiveRecord::Base
  validates :short_url, presence: true
  validates :long_url, presence: true
end

We won't add any URL validation rules, but this would be a good challenge to test your Ruby skills, right? Go ahead and add such a rule and see how it works out for you. In the meantime, your app structure should look like this:

.
├── app.rb
├── config
│   ├── application.rb
│   └── database.yml
├── config.ru
├── db
│   ├── app.sqlite3
│   ├── migrate
│   │   └── XXXXXXXXXXX_create_short_links.rb
│   └── schema.rb
├── Gemfile
├── Gemfile.lock
├── helpers.rb
├── models
│   └── short_link.rb
├── public
│   └── css
├── Rakefile
├── stylesheets
│   └── style.scss
└── views

Working with Sinatra views

For us to work with the URLs, we'll need a view with a form in it that allows a user to input a long URL and have a short version generated once they submit the form. First, we need to add(or modify) the relevant routes to app.rb:

# app.rb

...

class App < Sinatra::Base
  ...

  get '/' do
    @link = ShortLink.new

    erb :index
  end

  post '/links' do
    generated_string = SecureRandom.alphanumeric(12)

    link = ShortLink.create(
      long_url: params[:long_url],
      short_url: generated_string
    )

    link = ShortLink.create(
      long_url: params[:long_url],
      short_url: short_url_string
    )

    redirect to("/show/#{link.id}")
  end

  get '/show/:id' do
    @link = ShortLink.find(params[:id])

    erb :show
  end

  get '/links/:short_url' do
    @link = ShortLink.find(params[:short_url])
    link_visits = @link.visits.to_i
    @link.update(visits: link_visits + 1)

    redirect to("http://localhost:9292/#{link.long_url}")
  end

  ...

end

Let's break down what's going on. Beginning with the root route, we define an instance variable called @link, which we'll use in the form in the view. We also define a POST route, which deals with creating the ShortLink model and saving the data to the database and then uses the redirect to helper to redirect to the created ShortLink show view.

A point of note is that we use Ruby's SecureRandom to generate a string to represent our short URL string. There are many ways to generate unique strings in Ruby, so take some time to explore other options and use the one that works best for you.

Next, under the views folder, create two files: index.erb and layout.erb. Start with the layout file and edit it with a simple ERB boilerplate as shown below:

<!-- views/layout.erb -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Sinatra URL Shortener App</title>
</head>
<body>
  <%= yield %>
</body>
</html>

Notice the use of the <%= yield %> statement like the one used in a Rails app layout; it will render any dynamic content we have.

Next, open the index.erb file and edit it as shown below:

<section class="section is-primary">
  <div class="container">
    <div class="box">
      <h1 class="title is-2">Create Short URL</h1>
      <form action="/links" method="POST">
        <div class="field">
          <label class="label">Name</label>
          <div class="control">
            <input type="text" name="short_link[long_url]" value="#{@link.long_url}" class="input">
          </div>
        </div>
        <div class="field">
          <div class="control">
            <input type="submit" value="Save Link" class="button is-link">
          </div>
        </div>
      </form>
    </div>
  </div>
</section>

Note: We are using Bulma styles here, but you can use whatever you like for styling your app.

Next, we need a view template to render show view. Create a new file, show.erb, under the views folder and edit it as shown below:

<!-- views/show.erb -->

<section class="section">
  <div class="container">
    <h2>Short Link Details</h2>
    <p>Long URL: <%= @link.long_url %></p>
    <p>Short URL: <%= @link.short_url %></p>
    <p>No. of visits: <%= @link.visits %></p>
  </div>
</section>

Running the app

At this point, you have a functioning app that takes a long URL and generates a shortened URL string version. You can run the app using the command rackup in the terminal and visit http://localhost:9292/ to see the app running:

Sinatra app running

Create a few links; then, try to visit the shortened URL version and see how the redirect happens.

Wrapping up and next steps

In this tutorial, we have learned how to build a simple URL-shortening app using Sinatra. As you've seen, Sinatra is a very powerful Ruby framework that can be used to build all sorts of apps. However, it is especially suited for micro-services and APIs. With the lessons you've learned as while following the tutorial, challenge yourself to build something even more exciting.

Happy coding!

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Aestimo Kirina

    Aestimo is a family guy, Ruby developer, and SaaS enterpreneur. In his free time, he enjoys playing with his kids and spending time outdoors enjoying the sun, running, hiking, or camping.

    More articles by Aestimo Kirina
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial
    Are you using Sentry, Rollbar, Bugsnag, or Airbrake for your monitoring? Honeybadger includes error tracking with a whole suite of amazing monitoring tools — all for probably less than you're paying now. Discover why so many companies are switching to Honeybadger here.
    Start free trial
    Stop digging through chat logs to find the bug-fix someone mentioned last month. Honeybadger's built-in issue tracker keeps discussion central to each error, so that if it pops up again you'll be able to pick up right where you left off.
    Start free trial
    “Wow — Customers are blown away that I email them so quickly after an error.”
    Chris Patton, Founder of Punchpass.com
    Start free trial