How to Build a Shortlink App with Ruby and Redis

How to Build a Shortlink App with Ruby and Redis

Tutorial Details
    • Technologies: Ruby, Sinatra and Redis
    • Difficulty: Intermediate
    • Estimated Completion Time: 30 minutes

Final Product What You'll Be Creating

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!

Charlie Somerville is charliesome on Codecanyon
Tags: Ruby
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://mygeekzone.com Sam G. Daniel

    Great tutorial and introduction to Redis. Never realized how simple it was to scale a Redis database.

    Looking forward to more Rails Tutorials.

  • http://indiqo.eu Maximilian Bartel

    Great tutorial, thanks!

    I’ve been looking into Ruby and Sinatra for a while already and it’s stunning how easy it is to develop simple web applications based upon it.

    The only thing which holds me back from using it for slightly larger projects is that I couldn’t figure out how to develop an efficient user system incl. registration, login, simple account management and database storage.

    Padrino seems to be an option but I like the simplicity and minimalism of Sinatra itself.

    Anyway, hopefully we’ll see more Sinatra tutorials here in the future! :-)

  • http://www.expertmagentodevelopers.com Neha Verma

    nice explanation

  • http://blog.lastrose.com LastRose

    Been looking at ruby for while, and this tutorial display’s a level of simplicity that I don’t think I could accomplish with the other languages I know. Great tutorial, Thank You

  • http://cyclingjourney.extrast.com Brandon Hansen

    Great job keeping this one simple! Only nitpick- maybe I am missing something but isn’t there a great chance of collision here? Maybe that wasn’t the point of the tutorial, but just in case anyone wants to use this in production environments might want to add some unique validation in.

  • Jesse

    So we store every URL in Redis even if it already exists? That seems unnecessary to me. If someone already shortened a URL, and we know it, why not get that from Redis? I realize the key in the DB is the random string, so the current design doesn’t support that. Rather than store the string as the key, couldn’t you run the URL through some algorithm to hash it, and use that as the key?

    Don’t know how much, if any, of a perf hit that might be but it just seems so unnecessary to potentially have multiple entries in Redis for the same original URL.

  • Jo

    Nice tutorial.

    @Jesse

    That’s true. IMHO one could simply ask for the url before adding an new record and if there is one already return that.

    @Maximilian Barthel

    Yep, nearly all projects need authentication. A very good start to roll your own are the Railscast. i.e.:
    http://railscasts.com/episodes/250-authentication-from-scratch
    It’s for rails but if you use Sinatra with ActiveRecord the transfer should be easy.
    You’ll get an easy to maintain Authentication-System without any code that creates questionsmarks on your forehead ;-)

  • Tom

    Did anybody else find it a little humorous that the example short URL is longer than the original given?

  • http://www.designfahai.com Joseph

    Ahh programming is so tough, let me be a designer only :)

  • Colin

    Looks so simple, thanks for sharing.

    Does the random string include uppercase as well as lowercase?

    If so, does that mean that it could allow (26+26+10) x itself 5 times = upto ~1billion entries. Is that right, just from 5 random characters?

  • Max

    Does any one have a .zip of this?

  • http://www.charlie.bz Charlie Somerville
    Author

    @Max

    The code provided is literally all there is to it, a zip file shouldn’t really be necessary :)

    @Jesse

    I didn’t bother checking if the same URL is already in the database. This does not impact performance as Redis’ GET command always runs it constant time, regardless of the number of keys in the database.

    The lack of collision checking is a slight issue with the current code, although it is a simple fix to check for collisions if need be. The number of shortlink combinations here is 36^5, or 60,466,176, so collisions will be unlikely for quite a while. However, if there is a collision, the use of SETNX means it does not impact the existing shortlink.

  • Nag

    Thanks it is a great Article.

    I see a page cannot be found error on http://localhost:4567

    I am sure i am missing something Sorry i am new to Ruby and getting started.

    How do i start the service to run on port 4567 ?

    Tks, Nag

  • Ammar

    Hi,

    Just update to Lion, and was wondering on how I would go about setting up Ruby 1.9.2, I can’t seem to find a guide for this.

    Any help would be amazing.

    Thanks :)

  • http://www.marioplanet.com Zach

    Hey everyone,

    For anyone who would enjoy a node.js version of this tut please have a look at my github repo:

    https://github.com/qcom/LittleLink

    Please leave some input, and enjoy!

  • A. Scott

    Hi folks I have installed latest ruby/rails/sinatra/redis on my XP virtual machine. Things are running file. But when I put in a URL and hit “shorten” button I get the following error:

    RuntimeError at /
    ERR unknown command
    file: client.rb location: call line: 47

    and the faulting line is:

    redis.setnx “links:#{@shortcode}”, params[:url]

    please help me :(

    • A. Scott

      Sorry folks I was using ancient version of redis :)

  • yaaan

    Hey Charlie,

    Very nice tutorial :) To be hones it’s my first contact with ruby, sinatra and everything but I’m surprised how cool it works!

    I run my tests with shotgun filename.rb and every time when i want to shorten my url this error comes up: Errno::ECONNREFUSED – Connection refused – Unable to connect to Redis on 127.0.0.1:6379

    I already installed system timer but it doesn’t work… do you have a solution for me please? :)

    • http://www.chdesign.com.br Caio Ferreira

      Hey yaaan!

      U have to start the redis before.. Do it in command line:

      redis-server

      and play the WEBrick, i use the shotgun to do it.

      —-

      Install shotgun gem before (in command line):

      gem install shotgun

      and start WEBrick after (in command line):

      shotgun yourfile.rb

      just it : )

  • http://erickel.ly Eric Kelly

    I wrote a similar tutorial to make your own URL Shortener using Sinatra and DataMapper. It’s always good to see how somebody else solved the same problem.

  • vivek

    i know ruby but my doubt can i make a simple website with just ruby and not rails….

  • vivek

    in ruby is it possibe to make gui’s without using gems like “wxruby” .if it is please write a tutorial about ruby
    gui and windows apps with ruby