Redis URL shortner in Go hosted on heroku

Hope you have read my previous article on testing redis on heroku & App engine .

[wpi_designer_button text=’Download’ link=’’ style_id=’48’ icon=’github’ target=’_blank’]


[wpi_designer_button text=’Preview’ link=’’ style_id=’48’ icon=’cloud’ target=’_blank’]

Features Included:

1. Used online redis provided by redislabs

2. Proper folder structure ( I believe so)

3. Shows notifications in go

4. Added support for clipboard.js

I hope you have covered the basics of golang. Infact this youtube channel is highly recommended.

Get Started:

Directory structure: I wanted to in-cooperate MVC architecture. I didn’t use Go Web Frameworks as i wanted to learn the in and outs of the project. I hope this project structure is correct. ( Correct me if I am wrong)

The app has got 3 pages, namely – app, about, 404

MVC can be identified as

Model :- They are kind of data structures that deal with the database. It includes the db functions.

View :- They are those UI elements that are shown on screen.

Controllers :- They are related the functions specific to a page.

Pretty much similar to Ionic right ?

Public :- It is where you place all the public files accessible to viewers.


We can template common layout attributes and include them in every page.


{{define "header"}}
<!DOCTYPE html>
<html lang="en">
	<meta charset="UTF-8">
	<title>{{.Title}}</title> <!--Note this part -->

	<link  href="/public/css/main.css"    type="text/css"     rel="stylesheet">



{{define "footer"}}






{{define "navigation"}}
<div id="nav">
            <a href="/about">About</a>
            <a href="/">App</a>


{{template "header" .}} <!-- By the . we are passing page data to the header.html also -->
{{template "navigation" .}}

	<div class="wrap">
		<form action="" class="container" method="POST">
		  <input value="{{.Long_url}}" name="long_url" class="urlTerm" type="text"
				 placeholder="Long URL, eg :- ">
		<div class="container">	
			<input  id="shortenURL" readonly type="text" class="copyTerm" value="{{.Short_url}}" placeholder="Short URL" />
			<!--data-clipboard-target="#shortenURL" will be used by clipboard.js -->
			<button data-clipboard-target="#shortenURL" class="copyButton">
				<i class="fa fa-clipboard" aria-hidden="true"></i>


{{template "footer" .}}

2. public / js / main.js

var clipboard = new Clipboard('.copyButton'); // initializing Clipboard object

//Handles on success of copy function
clipboard.on('success', function(e) {

// Notification is handled using jQuery
// These functions will be called from go. Similar to echo "<scripts>...</scripts>" in PHP
function showSuccessMessage(message){
    $.notify(message, "success");

function showInfoMessage(message){
    $.notify(message, "info");

function showWarningMessage(message){

function showErrorMessage(message) {
    $.notify(message, "error");

3. Server.go

package main


// This is passed to every page to set the page details
type pageData struct {
	Title string
	Short_url string
	Long_url string

var tpl *template.Template 
var page_data pageData
var host_name string = ""

// used for showing notification popup using js
var notify_type int
var notify_msg string

func init() {
	tpl = template.Must(template.ParseGlob("views/*.html")) // Parses only html files from view folder
        models.Redis_db_init() // initialise the db connection

func main(){
	// This is core Handler that server all the files in the public folder with appropraite content type.
	// Else for eg :- png will be loaded as document type, and hinder page load
	http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("public")))) 
	//Handlers to handle url regex 
	http.HandleFunc("/", HomeHandler)
	// We are lisening to port that is set in the heroku environment using os.Getenv
	http.ListenAndServe(":"+os.Getenv("PORT"), nil) 

func ErrorHandler(w http.ResponseWriter, r *http.Request) {
	page_data = pageData{Title:"404"} // setting page title
	tpl.ExecuteTemplate(w, "error.html",page_data) // opens error.html page

func AboutHandler(w http.ResponseWriter, r *http.Request) {
	page_data = pageData{Title:"About"}
	tpl.ExecuteTemplate(w, "about.html",page_data)

func HomeHandler(w http.ResponseWriter, r *http.Request) {
	// for eg:- is the URL 
	//then shortCode = 1234 ( we are excluding the beginging / by using [1:] )
	shortCode := r.URL.Path[1:] 
	// The variables are static. So we need to re/initialize it every time
	page_data = pageData{Title:"Shortify",Short_url:"",Long_url:""}
	notify_type = 0 // notify-off : read the controllers.ShowNotifications

	if len(shortCode) != 0 {  // meaning we have a shortcode to check in the database
		if err != nil {
			redirect_url = host_name + "/404"

		controllers.RedirectTo(w,r,redirect_url) // redirect to long url


	}else if r.Method == "POST" { // Handles post data. If post url is correct, then save it to db

		long_url := r.PostFormValue("long_url") //get form data by html form id

		err := controllers.ValidateURL(long_url)// validate url

		if err != nil {
			notify_type,notify_msg  = 4, "Invalid URL." // we can set two variables in one line
			short_url := host_name + "/" + models.Redis_db_save(long_url)
			page_data = pageData{Title:"Shortify", Short_url:short_url ,Long_url:long_url} // setting page data 

			notify_type,notify_msg  = 1, "URL shortified."

	tpl.ExecuteTemplate(w, "app.html",page_data) // page datas are set in view/.html at appropraite places
	controllers.ShowNotifications(w,notify_type,notify_msg) // run this after loading the page


4. models / redis_db.go

//Models are data structures for representing database concepts.
package models

import (

var redisPool *redis.Pool // creating redis pool enables us to reuse redigo connections

func Redis_db_init(){
	redisAddr :=  ""
	redisPool = &redis.Pool{
		Dial: func() (redis.Conn, error) {
			conn, err := redis.Dial("tcp", redisAddr)
			return conn, err

func Redis_db_save(long_url string) (string) {

	// Reusing redisConn
	redisConn := redisPool.Get()
	defer redisConn.Close()

	new_short_code := controllers.Hash(long_url) // Hash the long url , ie number code
	redisConn.Do("SET", new_short_code, long_url) // Save the has along with long url into db
	return fmt.Sprint(new_short_code) // .Sprint => String print converts long to string

func Redis_db_get(shortCode string) (string,error) {
	redisConn := redisPool.Get()
	defer redisConn.Close()

	redirect_url, err := redis.String(redisConn.Do("GET", shortCode))
	return redirect_url,err

5. controllers / app.go

package controllers

import (

// Hash the long url into shortcode
func Hash(s string) uint32 {
	h := fnv.New32a()
	return h.Sum32()

// Use statusFound. It mainly deal with setting HTTP response data 
func RedirectTo(w http.ResponseWriter, r *http.Request, urlStr string){
	http.Redirect(w, r, urlStr, http.StatusFound)

// Checks if it is a valid url, ie
func ValidateURL(long_url string) (error){
	_,err := url.ParseRequestURI(long_url)
	return err

// Setting notify_script based on notify_type
// no-notify = 0 which is default, that means exit the function .
func ShowNotifications(w io.Writer,notify_type int,msg string)  {
	var notify_script string = ""
	switch notify_type {
	case 1:
		notify_script = "<script>showSuccessMessage(\""+msg+"\")</script>"
	case 2:
		notify_script = "<script>showInfoMessage(\""+msg+"\")</script>"
	case 3:
		notify_script = "<script>showWarningMessage(\""+msg+"\")</script>"
	case 4:
		notify_script = "<script>showErrorMessage(\""+msg+"\")</script>"

	fmt.Fprint(w,notify_script) // Fprint => File Print ie it writes this into the html files, once loaded.

I hope i explained something valuable with this tutorial. If i am wrong, do correct me.

To deploy this app on heroku or app engine, follow my previous article.

Happy coding.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s