Advertisement

How to Build a Shortlink App with Ruby and Redis

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

In this tutorial, we'll be building a quick shortlink web app with Ruby, the Sinatra web framework, and the Redis database. By the conclusion of this tutorial, you'll end up with a dead simple, high performance shortlink webapp that's super easy to scale.


Step 1. Getting Started

To follow along with this tutorial, you'll need Ruby installed on your system (I'm using 1.9.2), as well as the sinatra and redis gems, and Redis.

If you don't already have Ruby installed on your system, then you should be able to install it relatively easily. OS X, Debian or CentOS users may need to compile a newer version of Ruby. It's a pretty straightforward process.

Refer here to learn about how to install Ruby, via RVM.

Now you'll need to install the required Ruby Gems. Gems are a convenient way of installing virtually any Ruby library available. Simply type the following in your terminal window to install the required gems:

	gem install sinatra redis

We'll also need to install and compile Redis. Don't worry, it's really small and only takes roughly 15 seconds to compile on my machine.

	wget http://redis.googlecode.com/files/redis-2.0.4.tar.gz
	tar zfx redis-2.0.4.tar.gz
	cd redis-2.0.4
	make
	sudo make install
	cd ..

You can run the Redis server by typing redis-server into your terminal, and if you feel like playing around with Redis, give redis-cli a go.


Step 2. Building the App

One of the great things about Sinatra is how quick and easy it makes whipping up simple little apps - it's almost silly!

The code for the shortlink app itself won't be very long, so it should be really easy to understand. Don't worry if you don't understand it at first, I'll explain how it all works shortly.

Make a folder for your webapp - I've called mine redis-ruby-shortlink - and put the following files in it.

shortlink_app.rb

	require 'sinatra'
	require 'redis'

	redis = Redis.new

	helpers do
	  include Rack::Utils
	  alias_method :h, :escape_html

	  def random_string(length)
	    rand(36**length).to_s(36)
	  end
	end

	get '/' do
	  erb :index
	end

	post '/' do
	  if params[:url] and not params[:url].empty?
	    @shortcode = random_string 5
	    redis.setnx "links:#{@shortcode}", params[:url]
	  end
	  erb :index
	end

	get '/:shortcode' do
	  @url = redis.get "links:#{params[:shortcode]}"
	  redirect @url || '/'
	end

That's it. Pretty simple, eh?

In that little Sinatra app above, I've done a few key things. In the first two lines, I'm bringing in the libraries we need - sinatra and redis. On line 4, I establish a connection to the Redis server, listening on localhost. The lines after this is where it all starts to get interesting!

In Sinatra, you can specify helpers that are executed every time one of your routes (those get and post parts) is run. We can put anything that we might need often in the helpers block. In my helpers block, I've aliased h to Rack's escape_html, and defined a method to generate a random alphanumeric string of a certain length.

Next up are the routes. The first route is rather simple. Whenever a client makes a GET request to /, it just renders the index.erb page (I've included the source to this further down.)

The next route is where the good stuff happens. First, we make sure that the user has actually typed a URL into the URL box. If so, we generate a random shortcode five characters long by calling the random_string method we defined before. Then, we tell Redis to setnx (Set if n exists), a key representing our shortcode to its URL. Redis is a really fast and simple key/value database, or a NoSQL database. These databases are designed for really heavy key/value lookup operations, and as they drop most of the complexity of SQL, they can do it much faster. The 'links:' part of the key isn't strictly required, but it's good practice to split your Redis keys into namespaces so if you decide later on to store more information in the same database, you don't have to worry about clashes. After all that, we render the same index.erb page as before. Notice how if the user doesn't enter anything, this route does the same thing as the previous route.

The final route is run when a client visits a shortlink. This route introduces what's called a URL parameter. For example, when a client visits '/foobar', the :shortcode part of the route matches 'foobar'. We can access URL parameters the same way as any other parameter - the params hash. We look up the shortcode in the Redis database. If there's no such key as what we are trying to access, Redis will return nil. The next line redirects to either the URL we grabbed out of Redis (if it exists), or redirects to the homepage if not.

views/index.erb

index.erb is mostly boring markup, although it does have a few lines I'd like to point out. erb stands for embedded Ruby, and allows us to mix Ruby and HTML, like you would with PHP.

	<!DOCTYPE html>
	<html>
	<head>
		<title>Shortlink App</title>
		<style>
		body {
			font-family:"Gill Sans", "Gill Sans MT", Sans-Serif;
		}
		.container {
			width:400px;
			margin:120px auto 0px auto;
		}
		h1 {
			width:400px;
			margin-bottom:12px;
			text-align:center;
			color:#ff4b33;
			font-size:40px;
			padding-bottom:8px;
			border-bottom:2px solid #ff4b33;
		}
		form {
			display:block;
			width:400px;
		}
		input {
			display:block;
			float:left;
			padding:8px;
			font-size:16px;
		}
		#url {
			width:280px;
			margin-right:12px;
		}
		#submit {
			width:88px;
			border:none;
			background:#ff4b33;
			padding:10px;
		}
		#submit:hover {
			background:#ff7866;
		}
		.clear {
			height:1px;
			width:400px;
			clear:both;
		}
		.result {
			clear:both;
			width:400px;
			margin-top:12px;
			border-top:2px solid #ff4b33;
			padding-top:12px;
			text-align:center;
		}
		.result a {
			font-size:24px;
			display:block;
			margin-top:8px;
			color:#ff2d11;
			background:#ffd2cc;
			padding:8px;
		}
		</style>
	</head>
	<body>
		<div class="container">
			<h1>shortlink app</h1>
			<form method="post">
				<input type="text" value="<%= h(params[:url]) %>" name="url" id="url" />
				<input type="submit" value="shorten" id="submit" />
			</form>
			<div class="clear"></div>
			<% if @shortcode %>
			<div class="result">
				Your shortened URL is:
				<a href="http://my-shortlink-app.com/<%= @shortcode %>">http://my-shortlink-app.com/<%= @shortcode %></a>
			</div>
			<% end %>
		</div>
	</body>

One difference between erb and PHP that you may have already noticed (apart from the different languages) is that, where PHP uses <? and <?=, erb uses <% and <?=. The only interesting things about index.erb is the if block that only renders the part of the page that shows the shortlink if the @shortcode variable is defined. This lets us use the same view for everything. Another point of note is that we've made sure to HTML escape params[:url], so that we don't fall victim to an XSS attack. Other than those points, it's essentially a stock standard webpage.


Step 3. Scaling Up

One thing I briefly mentioned in this tutorial's introduction is how easily we can scale, thanks to Redis. Whereas scaling out to multiple SQL databases is a complicated affair, scaling Redis is actually quite trivial. This is a direct result of Redis' simplicity. If you need to scale to multiple Redises, add the following to your Redis configuration:

	slaveof master-redis-server.my-shortlink-app.com 6379

Once you have multiple slaves set up, it's a tiny little tweak to the Sinatra app above to make each Sinatra instance connect to a random Redis server (if you're at the stage where you need to scale Redis, I'm going to assume that you've already had to deploy multiple Sinatra instances. ;)


Conclusion

I hope this tutorial proved to be useful to you, whether you want to run your own shortlink service, or you're simply interesting in the latest cutting-edge technologies that are available to us web developers. I've covered some pretty neat software in this tutorial that's been incredibly useful to not just me, but thousands of other developers out there. Enjoy your tinkering!

Advertisement