Building a URL Shortener Using Go and Sqlite

Building a URL Shortener Using Go and Sqlite

An Introduction to Building a URL Shortener in Go With Sqlite Using Gorm

Introduction

A URL shortener is like the "Hello World" of Backend Development. I know you're going to say no, it's building a CRUD application, but come on, we've all used URL shorteners all our lives, and it's interesting to see how they work under the hood.

To give you an idea of how URL shorteners work, take a look at bitly.com, and T.LY. They offer some nice extra features like analytics, etc., but I won't focus on that in this article.

Today I will be building a simple URL shortener that will accept an input URL and output a shortened version of it. The application will generate a short code for the URL and store it in the SQLite database. When a user accesses the short URL, the application retrieves the corresponding long URL from the database and redirects the user to the long URL.

Why Go?

I find Go easy to read and understand. Being a Java developer myself, I absolutely hated its verbosity. Go, on the other hand, is concise and also has an amazing collection of in-built libraries and a good package manager. But again, this is a personal choice, and you can choose any language you are comfortable with.

Why SQLite?

With SQLite, you just need a file that acts as your database. I didn't want to deploy my database, and I wanted a cheaper solution that I could implement myself. The goal of this project is to understand the logic behind a URL shortener and the GORM library.

Project Setup⚒

Let's set up the project and install all dependencies that will be needed along the way.

Create a new project directory named "urlshortener" using your terminal.

  •                 $ mkdir urlshortener
    
  • Initialize the Go project, please make sure you have the latest version of Go installed in your system.

  •                 $ cd urlshortener/
                    $ go mod init github.com/your-username/urlshortener
    
  • Now open the project in VScode

  •                 $ code .
    
  • Create a new folder on the same level as main.go called shortenurl. Also, create a file named urls.db in the root folder.

  • Create files redirect.go, shortenurl.go and sql.go inside the shortenurl folder.

  • This is how the final tree structure of urlshortener will look after you add all the files. I will discuss each file in detail later.

  •                 pratimbhosale@Pratims-MacBook-Air ~ % tree urlshortner 
                    urlshortner
                    ├── go.mod
                    ├── go.sum
                    ├── main.go
                    ├── shortenurl
                    │   ├── redirect.go
                    │   ├── shortenurl.go
                    │   └── sql.go
                    └── urls.db
    

I need to store the details of the URL in a single field so that I can access all the variables easily. So I will store the URL details in the form of a struct.

Redirecting the URL

Go toredirect.go and create a struct of type URL. Don't forget to add the package shortenurl.

package shortenurl

type URL struct {
    ID        uint   
    Original  string 
    Shortened string
}

Shortening the URL

Go to shorten.go and write a function to shorten the URL.

rand.Intn(26) returns a random number between 0 and 25. 97 is the ASCII value of 'a'. So rand.Intn(26) + 97 returns a random lowercase letter.

This gives me a random string of size six to be attached to the base URL.

In this case, the base URL is http://localhost:8080/

package shortenurl

import (
    "fmt"
    "math/rand"
)

func ShortenURL(url string) string {
    // create a random string of size 6 from the alphabet
    s := ""
    for i := 0; i < 6; i++ {
        s += string(rand.Intn(26) + 97)
    }

    shortendURL := fmt.Sprintf("http://localhost:8080/%s", s)
    return shortendURL
}

Before I write the logic to redirect the short link to the original link, let's open an SQLite database connection that will store all the short links and the original links it redirects to. The database will be located at urls.db

I will be using the GORM library in order to interact with the database. Gorm is an amazing library that Go developers can use to perform common database operations without raw SQL.

Opening the database connection

In the sql.go file, I will open a new database connection using the Connect function.

gorm.Open() is used to open a connection to an SQLite database stored in the fileurls.db. The Open method takes the connection and configuration methods as input and returns the database connection instance. If the database does not exist, the Open method creates one in your working directory.

package shortenurl

import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

func Connect() (*gorm.DB, error) {
    db, err := gorm.Open(sqlite.Open("urls.db"), &gorm.Config{})
    if err != nil {
        return nil, err
    }
    return db, nil
}

Let's go back to the redirect.go file and create a method to redirect the short URL to the original URL.

GORM is a code-first ORM. This means you won’t have to define your schema in SQL. For GORM, you use Go structs to describe the schema and column types with GORM tags.

Let's specify that column ID is the primary key, and Original and Shortened will not accept a null value.

type URL struct {
    ID        uint   `gorm:"primary_key"`
    Original  string `gorm:"not null"`
    Shortened string `gorm:"not null"`
}

I shortened the URL by prefixing it with a random string and will retrieve the old URL by accessing the same string from the shortened URL.

The db.First method takes a pointer to a struct and a query and returns the first record that matches the query.

package shortenurl

import (
    "net/http"
    "gorm.io/gorm"
)

type URL struct {
    ID        uint   `gorm:"primary_key"`
    Original  string `gorm:"not null"`
    Shortened string `gorm:"not null"`
}

func RedirectURL(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
    id := r.URL.Path[1:]
    var url URL
    shortened := "http://localhost:8080/" + id
    db.First(&url, "shortened = ?", shortened)
    http.Redirect(w, r, url.Original, http.StatusFound)

}

The code r.URL.Path[1:] takes a slice of the r.URL.Path string, starting from the second character (index 1) to the end of the string. This is done to extract the random string of the URL to be redirected, which is at the end of the URL.

Integrating all the functions

Finally, let's get all of this running in our main.go file.

After adding all the required packages, open a database connection and assign it db.

A pointer to the database collection and call the Automigrate function to create a table in the database.


package main

import (
    "fmt"
    "github.com/timpratim/urlshortener/shortenurl"
    "net/http"
)

func main() {
    db, err := shortenurl.Connect()
    if err != nil {
        panic("failed to connect database")
    }
    db.AutoMigrate(&shortenurl.URL{})

    http.HandleFunc("/shorten", func(w http.ResponseWriter, r *http.Request) {
        original := r.FormValue("url")
        shortened := shortenurl.ShortenURL(original)
        fmt.Printf(shortened)

        db.Create(&shortenurl.URL{Original: original, Shortened: shortened})

    })
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        shortenurl.RedirectURL(db, w, r)
    })
    http.ListenAndServe(":8080", nil)
}

Testing our URL shortener

In the terminal run the command

go run main.go

Open a new terminal under the same project and the following curl command

curl -X POST -d "url=https://twitter.com/BhosalePratim" http://localhost:8080/shorten

The curl command makes a POST request to our API. ie http://localhost:8080/shorten with the original link, which in this case is a link to my Twitter profile.

The shortened URL will be written to the ResponseWriter.

You can also use a gorm logger to understand how the database is working to create your new URL and log all changes.

Conclusion

You now have your own URL shortener. I came up with the logic to assign random 6-digit strings to my base URL and redirect it to the original URL.

You can use different kinds of databases to build the same shortener. You can find the source code here

In the next article, I will build a basic frontend for my URL shortener, add test cases, improve the code quality, and build more versions of this to understand how to work with Go, Gorm, and different databases.

See you in Part 2! Until then, keep writing👩‍💻 and keep building❤️!

If you liked this blog or found any errors, do share your views and comments in the comment section.

You can follow me on Twitter at BhosalePratim for more updates about Golang, Backend Development and Developer Advocacy.