URL Shortner is one of the most used and popular online applications. A lot of people use it in order to save space while writing a message, email or may be a tweet. In this article, we will be building a URL Shortener with GoLang and Redis.
Just in case if you don’t know what is a URL shortener, It is a technique to shorten the URL and when you will use this Short Link it will redirect you to the origin URL.
So for example,
if you have this URL,
If we generate the short URL of this, it will be:
But before going any further, let’s take a look at the final outcome of this article.
How URL Shortner works?
There are two operations that URL shortener does, which are listed below,
- Generate a short URL based on the given URL.
- Redirect short URL to the original URL.
The task to generate the Sort URL from the Original URL is comprised of three steps.
=> The first step is to check the URL is valid or not.
=> The second step is to generate the random hash and assign it to the Original URL.
=> The Third step is storing the URL is Database by the name of the Hash Key.
The second step is to redirect short URL to the original URL, this step has two sub-steps, which are listed below:
=> When someone requests the short URL, we will look into our Database with the help of the Hash.
=> If the Original URL is found in the database attached to the Hash then we will redirect to the Original URL or else you will get an error message.
Alright, now let’s get our hands dirty and create the URL Shortener with GoLang and Redis.
This is not a production usage. It’s for understanding and creating proof of concept applications.
1. Understanding the project structure
Here we will give a very little bit of styling to our web application just to make it look presentable. And we won’t be using any frontend libraries or frameworks, for the sake of simplicity.
Since we are using plain Javascript and there is not much on the client-side to understand, hence we will focus on the Backend part only. Inside the public
folder, we will write down the Javascript scripts and inside view folder, we will write down the MARKUP.
Apart from the public folder we have only go files, let’s understand the purpose of each file why we need them.
db.go
:The file will have code to connect the Redis database.
routes-handlers.go
:This file will have the actual logic of the application. Here we will write code to store the Hash Key with the actual URL and the Redirection Logic will be handled here only.
routes.go
: As the name suggests, this file will contain the endpoints that we will define in this application.
server.go
: To create the Golang server we will use server.go file.
2. Creating a GoLang Server
Create aserver.goin the root of the project, which will be our entry point for the project. Here we will make a connection with the MongoDB database and we will define our application routes.
=>Inside themain()
function, First we are printing some information.
=>Then usingConnectDatabse()
function, we will make a MongoDB connection.
=>In the next line, we will createroute
variable, which will hold Route instance.
=>ThenAddApproutes()
function register application routes.
=>And at the end, usinghttp.ListenAndServe()
we will start our GO server.
server.go:
package main import ( "log" "net/http" "github.com/gorilla/mux" ) func main() { log.Println("Server will start at http://localhost:8000/") ConnectDatabase() route := mux.NewRouter() AddApproutes(route) log.Fatal(http.ListenAndServe(":8000", route)) }
3. Connecting GoLang to Redis
Create adb.goin the root of the project, Here we will connect our GoLang app with Redis database.
=>In the below code, first we have to include the Redis Go driver that you can install from here
=>Then we have created variablepublic
, which will hold the Redis connection instance. ThisClient
variable will be available inside all the files under themain
package.
=>Inside theConnectDatabase()
function we will create Redis connection as shown below,
db.go:
package main import ( "fmt" "github.com/go-redis/redis" ) // Client is exported Mongo Database client var Client *redis.Client // ConnectDatabase is used to connect the MongoDB database func ConnectDatabase() { Client = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set DB: 0, // use default DB }) _, err := Client.Ping().Result() if err != nil { fmt.Println(Client, err) } }
4. Adding GoLang routes in the application
1.Create aroutes.goin the root of the project, Here we will register application routes. Here we will usegorilla/mux
package to register routes.
=>The functionsetStaticFolder()
will tell the GoLang route that/public
folder will contain all the static files.
=>ThenAddApproutes()
function will register all the routes in the application. Here we have only one route to add which will be used by the FrontEnd javascript.
routes.go:
package main import ( "log" "net/http" "github.com/gorilla/mux" ) func setStaticFolder(route *mux.Router) { fs := http.FileServer(http.Dir("./public/")) route.PathPrefix("/public/").Handler(http.StripPrefix("/public/", fs)) } // AddApproutes will add the routes for the application func AddApproutes(route *mux.Router) { log.Println("Loadeding Routes...") setStaticFolder(route) route.HandleFunc("/", RenderHome) route.HandleFunc("/{urlShortCode}", RedirectIfURLFound).Methods("GET") route.HandleFunc("/getShortUrl", GetShortURLHandler).Methods("POST") log.Println("Routes are Loaded.") }
In the above code, we have registered two Routes:
/{urlShortCode}
: This route will handle the redirection logic./getShortUrl
: This route will handle storing the URL in the database.
5. Building a URL Shortener with GoLang and Redis
As I said earlier this application will have two parts, the first part is to store the URL in the database and the second part is the Redirection logic. First, we will store the URL in the database and then we will implement the logic for redirect to the Actual URL.
Storing the Actual URL in the Database
This step is the first part of the application and In this step, we will perform three substeps listed below.
=> The first step is to check the URL is valid or not.
=> The second step is to generate the random hash and assign it to the Original URL.
=> The Third step is storing the URL is Database by the name of the Hash Key.
Now open the routes-handlers.go and add the below code,
routes-handlers.go:
package main import ( "encoding/json" "fmt" "net/http" "net/url" "github.com/gorilla/mux" "github.com/ventu-io/go-shortid" ) // ErrorResponse is interface for sending error message with code. type ErrorResponse struct { Code int Message string } // RenderHome Rendering the Home Page func RenderHome(response http.ResponseWriter, request *http.Request) { http.ServeFile(response, request, "views/index.html") } // GetShortURLHandler This function will return the response based ono user found in Database func GetShortURLHandler(response http.ResponseWriter, request *http.Request) { type URLRequestObject struct { URL string `json:"url"` } type URLCollection struct { ActualURL string ShortURL string } type SuccessResponse struct { Code int Message string Response URLCollection } var urlRequest URLRequestObject var httpError = ErrorResponse{ Code: http.StatusInternalServerError, Message: "Something went wrong at our end", } decoder := json.NewDecoder(request.Body) err := decoder.Decode(&urlRequest) if err != nil { httpError.Message = "URL can't be empty" returnErrorResponse(response, request, httpError) } else if !isURL(urlRequest.URL) { httpError.Message = "An invalid URL found, provide a valid URL" returnErrorResponse(response, request, httpError) } else { uniqueID, idError := shortid.Generate() if idError != nil { returnErrorResponse(response, request, httpError) } else { err := Client.Set(uniqueID, urlRequest.URL, 0).Err() if err != nil { fmt.Println(err) returnErrorResponse(response, request, httpError) } var successResponse = SuccessResponse{ Code: http.StatusOK, Message: "Short URL generated", Response: URLCollection{ ActualURL: urlRequest.URL, ShortURL: request.Host + "/" + uniqueID, }, } jsonResponse, err := json.Marshal(successResponse) if err != nil { panic(err) } response.Header().Set("Content-Type", "application/json") response.WriteHeader(successResponse.Code) response.Write(jsonResponse) } } } // Helper function to handle the HTTP response func isURL(str string) bool { u, err := url.Parse(str) return err == nil && u.Scheme != "" && u.Host != "" } func returnErrorResponse(response http.ResponseWriter, request *http.Request, errorMesage ErrorResponse) { httpResponse := &ErrorResponse{Code: errorMesage.Code, Message: errorMesage.Message} jsonResponse, err := json.Marshal(httpResponse) if err != nil { panic(err) } response.Header().Set("Content-Type", "application/json") response.WriteHeader(errorMesage.Code) response.Write(jsonResponse) }
Explanation:
- At the very top of the file, we have
ErrorResponse
struct. This struct will be used multiple times in the application. - Then we have
RenderHome()
function which renders the Homepage of the application. isURL()
function will return boolean (true/false), based on the URL provided.- The
returnErrorResponse()
return the error response to the API. It expects three parametersrequest
,response
and custom error message along with the custom error code of typeErrorResponse
. - Now let’s talk about the elephant in the room the
GetShortURLHandler()
method, we have first declared the types and variables that we are going to use. - Then with the help of JSON decoder, we are reading the request body. In this request, we receive the Actual URL that we want to make short.
- After all the validation and checking if the URL is valid using
isURL()
function, we store the actual URL with random Hash in the redis database.
Redirecting to the Actual URL
In this step, we will redirect short URL to the original URL, this step has two sub-steps, which are listed below:
=> When someone requests the short URL, we will look into our Database with the help of the Hash.
=> If the Original URL is found in the database attached to the Hash then we will redirect to the Original URL or else you will get an error message.
Open the routes-handlers.go and add the below code,
routes-handlers.go:
// RedirectIfURLFound This function will redirect to actual website func RedirectIfURLFound(response http.ResponseWriter, request *http.Request) { var httpError = ErrorResponse{ Code: http.StatusInternalServerError, Message: "Something went wrong at our end", } urlShortCode := mux.Vars(request)["urlShortCode"] if urlShortCode == "" { httpError.Code = http.StatusBadRequest httpError.Message = "URL Code can't be empty" returnErrorResponse(response, request, httpError) } else { ActualURL, err := Client.Get(urlShortCode).Result() if ActualURL == "" || err != nil { httpError.Code = http.StatusNotFound httpError.Message = "An invalid/expired URL Code found" returnErrorResponse(response, request, httpError) } else { http.Redirect(response, request, ActualURL, http.StatusSeeOther) } } }
Explanation:
- The above code is very short and sweet, so I’ll keep it simple. This function will be called when a user requests for the short URL. Without any question, this will be GET request and we will have one parameter that will be the short URL hash.
- First, we will read the Parameter sent in URL and we will validate if the short hash is empty or not.
- If the short Hash is empty then we will send an Error Response.
- If the short hash is valid and then we will run a query on the Redis database. If the Result is null then we will again send the error Response.
- And if the response is not null then we will redirect the user to the actual URL using
http.Redirect()
.
6. Implementing the frontend
For the front end, I haven’t used any frameworks or libraries since it was a very small application. So here we will use VanillaJs. The most important part of the front end is making an Ajax Call which you the short URL of the Actual URL.
Here is a markup of index.html, which is stored inside the views folder.
index.html:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style> body { font-family: 'Helvetica', sans-serif; color: #fff; margin: 0px; padding: 0px; background-color: #ffa07a; } .app__heading { padding-top: 2%; } h1 { text-align: center; } .app__url-converter { width: 70%; margin: auto;; padding: 5%; } input { max-width: 100%; padding: 10px; font-size: 18px; position: inherit; display: block; width: -webkit-fill-available; border: 0px; } button { margin-top: 10px;; width: 100%; padding: 11px; font-size: 26px; background: #5f1b00; color: #fff; border: 0px; } .app__shorturl-result { display: none; } .app__actual-url, .app__short-url { text-align: center; font-size: 20px; position: relative; width: 70%; margin: auto; margin-bottom: 150px; } .app__actual-url::before { position: absolute; content: ' \2193'; font-size: 132px; text-align: center; left: 0; right: 0; top: 15px; } </style> </head> <body> <div class="app__container"> <div class="app__heading"> <h1>URL Shortener app with GoLang and Redis</h1> </div> <div class="app__url-converter"> <input type="text" id="input" placeholder="Enter the URL" /> <button id="generate-button" >Generate</button> </div> <div class="app__shorturl-result"> <div class="app__actual-url"> https://github.com/go-redis/redis </div> <div class="app__short-url"> https://github.com/cfyhi </div> </div> </div> </body> <script src="/public/js/script.js"></script> </html>
If you notice, we have added script.js at the very end of the markup. This javascript file contains the code to make an Ajax request and get the short URL of the actual URL.
In the below code, we are making an ajax call and fetching the response and Based on the response we will display the results. Below is code for the script.js file,
script.js:
const urlInputBox = document.querySelector('#input'); const generateUrlButton = document.querySelector('#generate-button'); const resultContainer = document.querySelector('.app__shorturl-result'); const actualUrl = document.querySelector('.app__actual-url'); const shortUrl = document.querySelector('.app__short-url'); generateUrlButton.onclick = (event) => { const url = urlInputBox.value.trim(); getShortUrl(url); }; function getShortUrl(url) { fetch(`/getShortUrl`, { method: 'POST', headers: { 'Content-Type': undefined }, body: JSON.stringify({ url }) }) .then((resp) => resp.json()) .then((result) => { if (result.hasOwnProperty('Response')) { renderURLs(result['Response']); } }) .catch((error) => { console.log(error); }); } function renderURLs(response) { const { ActualURL, ShortURL } = response; resultContainer.style.display = 'initial'; actualUrl.innerHTML = ActualURL; shortUrl.innerHTML = ShortURL; }
7. Conclusion
In the above application, we studied how URL shortener works and is an example of how we can develop the URL shortener using GoLang.