Creating your own URL shortener
Tags: go, showdev, tutorial, githubcopilot
Published: 2022-05-31 16:30:02

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](https://go.dev/dl/) 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 box![Initialize this repository with Readme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9xhsb2dxz2cw11cgnezz.png) if you want to use Codespaces, after creating the repo click here to create a Codespace ![Create codespace on master](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/azk8qemyofsaiclvix6k.png).

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. 
```go
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!"))
}
```
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:
```go
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)
}
```

### 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:
```go
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)
}
```
### 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`](github.com/go-redis/redis/v8), to install it run `github.com/go-redis/redis/v8`.

To create the client we will use the next code 
```go
redis.NewClient(&redis.Options{
		Addr:     server,
		Password: password,
		DB:       0,
	})
```
For getting data from the database we will use the next code
```go
value, err := db.Get(context.Background(), key).Result()
```

Implementing this to our last code look like this:
```go
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)
}
```

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.