Go: how to create a URL shortener

Go: how to create a URL shortener

In this article, we will see how to implement a simple URL shortener using the Go programming language.

A URL shortener is a tool that allows you to reduce the length of a URL while maintaining its original functionality. It is particularly useful for sharing long links on platforms that limit the number of characters or simply to improve the aesthetics of the link. In this article, we will see how to implement a simple URL shortener using the Go programming language.

To follow this guide, you need:

  • A basic knowledge of Go.
  • Go installed on your system.
  • A configured development environment (e.g., VS Code, GoLand, etc.).

Before we start writing code, it’s important to understand how our URL shortener will work. The system will need to:

  1. Receive a long URL via an HTTP POST request.
  2. Generate a unique identifier for the URL.
  3. Store the association between the identifier and the original URL in a database.
  4. Return a shortened URL that points to our service.
  5. Redirect the user to the original URL when visiting the shortened URL.

Let’s start by creating a new folder for the project and setting up our Go environment.


mkdir url-shortener
cd url-shortener
go mod init url-shortener

We will create a simple HTTP server that will handle requests to shorten and resolve URLs.


package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", HomeHandler)
	http.HandleFunc("/shorten", ShortenHandler)
	http.HandleFunc("/resolve/", ResolveHandler)

	fmt.Println("Starting server on :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func HomeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome to the URL Shortener!")
}

To shorten a URL, we need to generate a unique identifier. A simple implementation might involve generating a random string. However, for simplicity and predictability, we can use an incremental counter and convert it into a short string.


import (
	"encoding/base64"
	"sync/atomic"
)

var idCounter uint64

func generateShortID() string {
	id := atomic.AddUint64(&idCounter, 1)
	return base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%d", id)))
}

Now that we can generate an identifier, let’s create the handler to shorten URLs. This endpoint will receive a long URL via a POST request and return the shortened URL.


import (
	"encoding/json"
	"io"
	"net/http"
)

var urlStore = make(map[string]string)

type ShortenRequest struct {
	URL string json:"url"
}

type ShortenResponse struct {
	ShortURL string json:"short_url"
}

func ShortenHandler(w http.ResponseWriter, r *http.Request) {
	var req ShortenRequest
	body, _ := io.ReadAll(r.Body)
	json.Unmarshal(body, &req)

	shortID := generateShortID()
	urlStore[shortID] = req.URL

	resp := ShortenResponse{ShortURL: fmt.Sprintf("http://localhost:8080/resolve/%s", shortID)}
	json.NewEncoder(w).Encode(resp)
}

Finally, let’s create the handler to resolve shortened URLs. This endpoint will redirect the user to the original URL.


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

	originalURL, exists := urlStore[shortID]
	if !exists {
		http.NotFound(w, r)
		return
	}

	http.Redirect(w, r, originalURL, http.StatusFound)
}

Conclusion

We have implemented a simple URL shortener using Go. Our system is very basic and could be extended with features such as data persistence in a database, adding an expiration for shortened URLs, and implementing a rate limiting system. However, this guide provides a solid foundation to understand the fundamental concepts and to create a more customized and robust version of a URL shortener.