DEV Community

Cover image for Creating your own URL shortener
Vic
Vic

Posted on

Creating your own URL shortener

This is a kind of half-blog half-tutorial of how to create your URL shortener using Redis, go & GitHub copilot.

Introduction

I was searching for a URL shortener service for my domain, there are a lot like bit.ly, short.io, cutt.ly... but most of them are paid or have a lot of limitations like the number of URLs.
So I decided to create my own, to start the project I had to know

  1. Which language I'll use
  2. How do I'm going to store the data For the first one, I decided to use Go. Why? Why not? It's a programming language so it works. For the second one I decided to use MongoDB but this will change shortly. Don't focus on that yet and start coding!

Setting up the workspace

You can download the go cli here but I'm more based than you 😎 so GitHub gave me the Codespaces beta
if you're based like me go to repo.new and create a new repo, make sure to check this boxInitialize this repository with Readme if you want to use Codespaces, after creating the repo click here to create a Codespace Create codespace on master.

To init your project run the next command go mod init shortener, this will create a go.mod. Something like a package.json in node or a Gemfile in rust.

Start coding

Now start coding!

Paste the next code into the main.go file.

package main
import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/hello", hello)
    http.ListenAndServe(":8080", nil)
}

func hello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World!"))
}
Enter fullscreen mode Exit fullscreen mode

This will create a http server in the port 8080 and reply to the endpoint /hello with Hello World!.

Redirecting

What a URL shortener does? redirecting the user to another URL.

To do this we will use the next line of code
http.Redirect(w, r, URL, http.StatusFound)

Implementing this to our last code look like this:

package main
import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/hello", HandleReq)
    http.ListenAndServe(":8080", nil)
}

func HandleReq(w http.ResponseWriter, r *http.Request) {
    http.Redirect(w, r, "https://www.google.com", http.StatusFound)
}
Enter fullscreen mode Exit fullscreen mode

Reding the url

To read the url we will use the next line of code
r.URL.Path[len("/"):].
Implementing this to our last code look like this:

package main
import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/hello", HandleReq)
    http.ListenAndServe(":8080", nil)
}

func HandleReq(w http.ResponseWriter, r *http.Request) {
    key := r.URL.Path[len("/"):]

    http.Redirect(w, r, "https://www.google.com", http.StatusFound)
    fmt.Println(key)
}
Enter fullscreen mode Exit fullscreen mode

Implementing the database

Now that we know how to read the url and redirecting we implement the database.

To do this I thought that using MongoDb is a good idea. Let me save you that suffering, do not use mongo.

For this, we will use Redis instead

Hosting the database

Do you know how to host a redis database? I do not
So for this, I'll use Railway, it lets you create a database in seconds, just go to https://railway.app/new & click Provision redis, then click on Redis -> Connect
There will be a URL like this redis://default:password@host, store this data.

ℹ This tutorial doesn't cover how to create URLs programmatically **yet* so for creating URLs go to data and create keys with the value of the URL where you want to redirect.*

Connecting to the database

For this we will use go-redis, to install it run github.com/go-redis/redis/v8.

To create the client we will use the next code

redis.NewClient(&redis.Options{
        Addr:     server,
        Password: password,
        DB:       0,
    })
Enter fullscreen mode Exit fullscreen mode

For getting data from the database we will use the next code

value, err := db.Get(context.Background(), key).Result()
Enter fullscreen mode Exit fullscreen mode

Implementing this to our last code look like this:

package main

import (
    "fmt"
    "net/http"
    "context"

    "github.com/go-redis/redis"
)
//This define db and context, why? In go if you want to make a global variable you need to define it outside the main function.
var db *redis.Client
var ctx = context.Background()

func main() {
    DataBase()
    http.HandleFunc("/", requestHandler)
    http.ListenAndServe(":"+port, nil)
    fmt.Println("Server started on port:", port)

}

func DataBase() {

    db = redis.NewClient(&redis.Options{
        Addr:     server,
        Password: password,
        DB:       0,
    })

    fmt.Println("Connected to Database")
}

func requestHandler(w http.ResponseWriter, r *http.Request) {
    //This get the key from the URL by wathing the url path and removing the `/`
    key := r.URL.Path[len("/"):]

    //this checks if the key is empty, if it's, return 404
    if r.URL.Path == "/" {
        http.Error(w, "Not Found", http.StatusNotFound)
        return
    }

    //this gets the value from the database
    value, err := db.Get(ctx, key).Result()

    //Go is kind of weir so if you want to know if there's a error you need to check if the value error value is not nil(null)
    if err != nil {
        panic(err)
    }
    //check if there is no URl for the key, if it's true, return 404
    if value == "" {
        http.Error(w, "Not Found", http.StatusNotFound)
        return
    }

    http.Redirect(w, r, value, http.StatusFound)
}
Enter fullscreen mode Exit fullscreen mode

That's it!
Now we have a completely functional URL shortener!

I hope you enjoyed this post, if you have any questions or suggestions feel free to PLEASE comment.

Top comments (0)