Building a URL Shortener in Go: A Step-by-Step Guide
Published: 2024-02-24 23:02:28

## Introduction:
* URL shortening is a technique used used to convert lengthy and complex URLs into shorter user-friendly versions. These shortened URLs are preferably advantageous compared to the original URLs, since they reduce visual complexity of the original URLs making them easier to read and remember thus promoting user engagement and click-through rates. Additionally, shortened URLs can be easily shared on spaces where available space is limited.
* In this article I am going to walk you through the process of building a URL shortener in Go.

## Prerequisites:
* Basic understanding of Go, HTTP servers and web development concepts.
* You must have Go installed in your computer system. If you do not have Go installed, first navigate to [link](https://go.dev/doc/manage-install) to install Go.

## Creating Project Directory and files:
1. Open terminal, create a project directory and name it "URL-Shortener".
2. Navigate inside the "URL-Shortener" directory.
3. Initialize a new Go module. 
4. Create a main.go file. Make sure the commands are entered line by line as shown below.

```
mkdir URL-Shortener
cd URL-Shortener
git mod init url-shortener
code main.go
```
## Writing the Go code:
* Open the main.go file. This is where you will write the code.
* Start off by declaring the main package. Import the necessary packages for running the program. "fmt" for formatting and printing, "math/rand" is for random number generation, "net/http" is for HTTP server functionality, "strings" is for string manipulation, and "time" for time-related functions.
```
package main
import (
	"fmt"
	"math/rand"
	"net/http"
	"strings"
	"time"
)
```
* Create a global variable "ShortURLs" which is a map that will store the shortened URLs of type string as key, and their corresponding original URLs of type string as value.

```
// ShortURLs is a map that stores shortened URLs with their corresponding original URLs.
var shortURLs = make(map[string]string)
```
* The main function sets up HTTP request handlers for different endpoints and starts the HTTP server on port 3030. The http.handleFunc() is used to map HTTP request paths to corresponding handler functions.

```
func main() {
	// Set up HTTP request handlers
	http.HandleFunc("/", handleForm)           //Handle root endpoint
	http.HandleFunc("/shorten", handleShorten) // Handle URL shortening endpoint
	http.HandleFunc("/short/", handleRedirect) // Handle redirecting to original URL

	// Start HTTP server
	fmt.Println("URL Shortener running on: 3030")
	http.ListenAndServe(":3030", nil)
}
```
* The handleForm function is the handler function for GET requests to the root endpoint ("/"). It displays an HTML form for submitting a URL that needs to be shortened.

```
// handleForm handles GET requests to the root endpoint and displays the URL shortening form
func handleForm(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodPost {
		http.Redirect(w, r, "/shorten", http.StatusSeeOther)
		return
	}

	w.Header().Set("Content-type", "text/html")
	fmt.Fprint(w, `
		<!DOCTYPE html>
		<html>
		<head>
			<title> URL-Shortener</title>
		</head>
		<body>
			<h2>URL-Shortener</h2>
			<form method= "post" action="/shorten">
				<input type="url" name="url" placeholder="Enter a URL" required>
				<input type="submit" value="Shorten">
			</form> 
		</body>
		</html>
	`)
}
```

* The handleShorten function is the handler for POST requests to the "/shorten" endpoint. It generates a shortened URL, stores the URL and its original counterpart in a map and renders an HTML response that displays both URLs.

```
// handleShorten handles POST requests to the /shorten endpoint and generates a shortened URL
func handleShorten(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
		return
	}

	// Get the original URL from the form
	originalURL := r.FormValue("url")
	if originalURL == "" {
		http.Error(w, "URL parameter is missing", http.StatusBadRequest)
		return
	}

	// Generate a short key and store the original URL
	shortKey := generateShortKey()
	shortURLs[shortKey] = originalURL

	// Construct the shortened URL
	shortenedURL := fmt.Sprintf("http://localhost:3030/short/%s", shortKey)

	// Render HTML response with original and shortened URLs
	w.Header().Set("Content-Type", "text/html")
	fmt.Fprint(w, `
		<!DOCTYPE html>
		<html>
		<head>
			<title> URL-Shortener</title>
		</head>
		<body>
			<h2>URL-Shortener</h2>
			<p>Original URL: `, originalURL, `</p>
			<p>Shortened URL: <a href="`, shortenedURL, `">`, shortenedURL, `</a></p>
		</body>
		</html>
		`)
}
```
* handleRedirect handles requests to shortened URLS ("/short"). It extracts the short key from the request path, looks up the original URL, and redirects the client to the original URL.

```
// handleRedirect handles requests to shortened URLs and redirects to the original URL.
func handleRedirect(w http.ResponseWriter, r *http.Request) {
	// Extract the short key from the request path
	shortKey := strings.TrimPrefix(r.URL.Path, "/short/")
	if shortKey == "" {
		http.Error(w, "Shortened key is missing", http.StatusBadRequest)
		return
	}

	// Retrieve the original URL from the map
	originalURL, found := shortURLs[shortKey]
	if !found {
		http.Error(w, "Shortened key not found", http.StatusNotFound)
		return
	}

	// Redirect to the orginal URL
	http.Redirect(w, r, originalURL, http.StatusMovedPermanently)
}
``` 
* The generateShortKey function is responsible for generating a short random key for URL shortening. It uses a predefined character set and a key length of 6 characters to create the short key.

```
// generateShortKey generates a short random key for URL shortening
func generateShortKey() string {
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	const keyLength = 6

	rand.Seed(time.Now().UnixNano())
	shortKey := make([]byte, keyLength)
	for i := range shortKey {
		shortKey[i] = charset[rand.Intn(len(charset))]
	}
	return string(shortKey)
}
```
## Executing the URL Shortener Program
* Save the main.go file.
* Run the program by typing on the terminal the command below:
```
go run main.go
```
* It will display that the URL Shortener is running on port 3030.
```
URL Shortener running on: 3030
```
* Open your browser and on the address bar type http://localhost:3030 then search.
* Once the new page loads, enter the URL you want to shorten in the text field, and click the "shorten" button.
* Find the shortened URL displayed on the screen.

## Resources.
* You can find the whole working code on my Github profile. Here is the [link](https://github.com/JosephOkumu/URL-Shortener)
* Also find a short video demonstration showing the end result on Twitter. Here is the [link](https://twitter.com/jaykush_0/status/1761349560041869736)