A Ten-minute URL Shortener

Share this article

Downstairs from SitePoint, the team at 99designs recently revamped its URL shortener to support a bunch of new features. Using a combination of Sinatra and Heroku, I was flabbergasted at how easy it was.

What We’re Building

Before I jump in, let me make it clear: we’re not building a full-blown URL shortener like bit.ly or TinyURL. Instead, we’re going to take a request like http://gentle-light-20.heroku.com/khw (Heroku will give you your own randomly generated, serene-sounding name for development purposes) and redirect the user to another URL. In this example, we’ll send the user to https://www.sitepoint.com/?p=26564, which WordPress will redirect to a friendlier URL.

I’ve also assumed you have the following installed on your system:

  • Ruby
  • the Gem package management system
  • the RSpec testing framework
  • Git

If you need help with these, Heroku’s Quickstart Guide has links to help you out.

A Note on Base 36 Numbers

In the above example, you might be wondering how we go from khw to 26564. Well, khw is a base 36 number.

If you’re familiar with hexadecimal color codes, you’ll be familiar with the idea of a base 16 counting system. That’s where digits can not only be between 0 and 9, but can also be A (which corresponds to 10), B (which corresponds to 11), and so on through to F (which has a value of 15).

A base 36 numbering system extends this idea so that a digit can be anything between 0 and 9 or A to Z. The table below gives some examples of base 36 numbers and their decimal counterparts:

Base 36 number Decimal value
9 9
A 10
B 11
Y 34
Z 35
10 36
1BC 47064

Step 1: Set Up Your Environment

To start, we’ll need to install some Gems. Gems are Ruby libraries, and can be installed by using the gem command. We’ll need to install the following gems for our application:

  • heroku to administer our application
  • rspec to test our application
  • sinatra to provide the framework for our application
  • rack and rack-test, which provide Ruby with an interface with the web server

To install these Gems, run the following commands:

$ sudo gem install heroku
Successfully installed rake-0.8.7
Successfully installed mime-types-1.16
⋮
Installing RDoc documentation for json_pure-1.4.6...
Installing RDoc documentation for heroku-1.10.5...
$ sudo gem install sinatra
Successfully installed rack-1.2.1
Successfully installed sinatra-1.0
⋮
Installing RDoc documentation for rack-1.2.1...
Installing RDoc documentation for sinatra-1.0...
$ sudo gem install rspec
**************************************************

Thank you for installing rspec-1.3.0

Please be sure to read History.rdoc and Upgrade.rdoc
for useful information about this release.

**************************************************
Successfully installed rspec-1.3.0
⋮
Could not find main page README.rdoc
$ sudo gem install rack
Successfully installed rack-1.2.1
1 gem installed
Installing ri documentation for rack-1.2.1...
Installing RDoc documentation for rack-1.2.1...
$ sudo gem install rack-test
Successfully installed rack-test-0.5.4
1 gem installed
Installing ri documentation for rack-test-0.5.4...
Installing RDoc documentation for rack-test-0.5.4...
$ 

Step 2: Write Some Tests

Now that we have our Gems installed, we can start writing some Ruby code. Let’s start with some simple tests. Create a directory called my-url-shortener. Then, inside a file called my-url-shortener-test.rb, put in the following:

require "my-url-shortener"
require "spec"
require "rack/test"

set :environment, :test

describe "My URL Shortener" do
	include Rack::Test::Methods

	def app
		Sinatra::Application
	end

	it "redirects to a blog post" do
		get "/123" # 123 (base 36) == 1371 (base 10)
		last_response.status.should == 301
		last_response.headers["location"].should == "https://www.sitepoint.com/?p=1371"
	end

	it "redirect to blog post with lowercase digits" do
		get "/a" # a (base 36) == 10 (base 10)
		last_response.status.should == 301
		last_response.headers["location"].should == "https://www.sitepoint.com/?p=10"
	end

	it "redirect to blog post with uppercase digits" do
		get "/A" # A (base 36) == 10 (base 10)
		last_response.status.should == 301
		last_response.headers["location"].should == "https://www.sitepoint.com/?p=10"
	end

	it "redirects to home" do
		get "/"
		last_response.status.should == 301
		last_response.headers["location"].should == "https://www.sitepoint.com/"
	end

	it "unrecognised path redirects" do
		get "/foo/bar"
		last_response.status.should == 301
		last_response.headers["location"].should == "https://www.sitepoint.com/foo/bar"
	end
end

In the above code, we’re testing the following redirects:

Requested path Redirect destination
/123 https://www.sitepoint.com/?p=1371
/A https://www.sitepoint.com/?p=10
/a https://www.sitepoint.com/?p=10
/ https://www.sitepoint.com/
/foo/bar https://www.sitepoint.com/foo/bar

Running these tests will only work if there’s a class called my-url-shortener, so let’s create that. Sinatra will do all of the work for us if we just create a file called my-url-shortener.rb with the following line:

require "sinatra"

Now we can run the test by entering spec my-url-shortener-test.rb at the command line:

$ spec my-url-shortener-test.rb
FFFFF

1)
'My URL Shortener redirects to a blog post' FAILED
expected: 301,
     got: 404 (using ==)
./my-url-shortener-test.rb:16:

2)
'My URL Shortener redirect to blog post with lowercase digits' FAILED
expected: 301,
     got: 404 (using ==)
./my-url-shortener-test.rb:22:

3)
'My URL Shortener redirect to blog post with uppercase digits' FAILED
expected: 301,
     got: 404 (using ==)
./my-url-shortener-test.rb:28:

4)
'My URL Shortener redirects to home' FAILED
expected: 301,
     got: 404 (using ==)
./my-url-shortener-test.rb:34:

5)
'My URL Shortener unrecognised path redirects' FAILED
expected: 301,
     got: 404 (using ==)
./my-url-shortener-test.rb:40:

Finished in 0.020694 seconds

5 examples, 5 failures

Here we can see that all five tests have failed, and our redirector just returns 404s. Let’s do something about that, shall we?

Step 3: Write Your Application

Add the following to my-url-shortener.rb:

get %r{^/([0-9a-zA-Z]+)$} do
	postid = params[:captures][0].to_i(36)
	redirect "https://www.sitepoint.com/?p=#{postid}", 301
end

These four lines:

  • intercept any request matching the regular expression ^/([0-9a-zA-Z]+)$ (that is, anything starting with a slash followed by one or more alphanumeric characters),
  • extract a Post ID from the incoming request, converting a base 36 number into an integer,
  • redirect the user to https://www.sitepoint.com/?p=postid, and
  • stop handling this request

Rerunning the test shows that we’ve made some progress:

$ spec my-url-shortener-test.rb
...FF

1)
'My URL Shortener redirects to home' FAILED
expected: 301,
     got: 404 (using ==)
./my-url-shortener-test.rb:34:

2)
'My URL Shortener unrecognised path redirects' FAILED
expected: 301,
     got: 404 (using ==)
./my-url-shortener-test.rb:40:

Finished in 0.0337120000000001 seconds

5 examples, 3 failures

The three dots in the first line of these results shows us that three of our tests passed. The two remaining cases can be handled by adding a catchall to the end of my-url-shortener.rb:

get "/*" do
	path = params["splat"]
	redirect "https://www.sitepoint.com/#{path}", 301
end

Let’s try running the tests now:

$ spec my-url-shortener-test.rb
.....

Finished in 0.008923 seconds

5 examples, 0 failures

Success!

Now, let’s deploy this sucker to Heroku.

Step 4: Deploy to Heroku

In order to deploy to Heroku, we’ll need to set up two config files: one called .gems, which contains a list of the Gems our application requires, and another called config.ru, which tells Heroku how to run our app. These files, like the others in this project, are super simple.

Put this in .gems:

sinatra

Put this in config.ru:

require "my-url-shortener"
run Sinatra::Application

Now we’re ready to deploy our application. If you’re yet to do so, visit Heroku and sign up. Once you’ve done this, run through the instructions in Heroku’s Quickstart Guide to create a basic Heroku application.

In Mac OS X, this boils down to:

$ cd ~/my-url-shortener/
$ git init
Initialized empty Git repository in /Users/craiga/my-url-shortener/.git/
$ git add .
$ git commit -m "Creating URL shortener application"
[master (root-commit) b602ee0] Creating URL shortener application
 4 files changed, 57 insertions(+), 0 deletions(-)
 create mode 100644 .gems
 create mode 100644 config.ru
 create mode 100644 my-url-shortener-test.rb
 create mode 100644 my-url-shortener.rb
$ heroku create
Enter your Heroku credentials.
Email: craiga@sitepoint.com
Password:
Uploading ssh public key /Users/craiga/.ssh/id_rsa.pub
Creating gentle-light-20... done
Created http://gentle-light-20.heroku.com/ | git@heroku.com:gentle-light-20.git
Git remote heroku added
$ git push heroku master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 940 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)

-----> Heroku receiving push
-----> Sinatra app detected

-----> Installing gem sinatra from http://rubygems.org
       Successfully installed sinatra-1.0
       1 gem installed

       Compiled slug size is 236K
-----> Launching.... done
       http://gentle-light-20.heroku.com deployed to Heroku

To git@heroku.com:gentle-light-20.git
 * [new branch]      master -> master

Boom! Your application has been deployed to Heroku. Check it out by visiting the URL Heroku gave you (in the above example, it’s http://gentle-light-20.heroku.com).

Step 5: There Is No Step Five!

And that’s that. If you have a short domain you’d like to use, such as the 99d.me domain we use here at 99designs, you can assign it to your website using the tools on Heroku’s site. At the time of writing, you’ll need to enter your credit card number, but there’ll only be a charge if you need lots of database space or better performance.

Craig AndersonCraig Anderson
View Author
Herokurubysinatraurl shortener
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week