Create a simple URL Shorteners in GO.

Introduction

URL shorteners are a popular web service that provides a shortened alias for long URLs, making them easier to share and manage. Services like Bitly and TinyURL have made URL shortening a common practice on the internet. In this tutorial, we will create a simple URL shortener in Go (Golang) that takes a long URL, generates a short hash for it, and stores the mapping between them. We will also cover how to handle URL redirection using the stored mappings.

This guide will walk you through setting up the server, handling HTTP requests, generating short URLs, and managing URL mappings with file storage for persistence.

Prerequisites

Before starting, make sure you have:

  • Go: Installed on your machine (version 1.16 or above is recommended).

  • Basic Knowledge of Go: Familiarity with Go's standard library, particularly HTTP handling, file I/O, and concurrency.

Feel free to add your customizations and features, your own configurations, etc.

Step-by-Step Implementation

1. Initialize the Project

To start, create a new directory for your project and create a file named main.go.

mkdir go-url-shortener
cd go-url-shortener
touch main.go
2. Import Necessary Packages

In main.go, import the necessary packages that will be used in our URL shortener service.

package main

import (
    "crypto/sha1"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "sync"
)
  • crypto/sha1: For generating a unique hash of the long URL.

  • encoding/hex: For encoding the hash to a hex string.

  • encoding/json: For converting the URL mapping to and from JSON format.

  • net/http: For handling HTTP requests and responses.

  • os and io/ioutil: For file I/O operations.

  • sync: For concurrent access to shared data.

3. Set Up URL Storage and Mutex

We need to store the mappings between short URLs and long URLs. We will use a Go map and a mutex to handle concurrent access safely.

var (
    urlStore = make(map[string]string) // In-memory store for URL mappings
    mutex    = &sync.Mutex{}           // Mutex to ensure safe concurrent access
)
4. Define the main Function

The main function initializes the server, loads any existing URL mappings from a file, and sets up HTTP routes.

func main() {
    // Load existing URL mappings from file
    loadURLMapping()

    // Define HTTP handlers
    http.HandleFunc("/shorten", shortenHandler)
    http.HandleFunc("/", redirectHandler)

    fmt.Println("Starting server on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}
  • loadURLMapping: Loads URL mappings from a file at the start.

  • shortenHandler: Handles requests to shorten a long URL.

  • redirectHandler: Handles requests to redirect to the original long URL.

5. Implement the Shortening Logic

The shortenHandler function reads the long URL from the query parameters, generates a short URL, stores the mapping, and returns the short URL to the user.

func shortenHandler(w http.ResponseWriter, r *http.Request) {
    longURL := r.URL.Query().Get("url")
    if longURL == "" {
        http.Error(w, "URL parameter is missing", http.StatusBadRequest)
        return
    }

    // Generate short URL
    shortURL := generateShortURL(longURL)

    // Store the mapping
    mutex.Lock()
    urlStore[shortURL] = longURL
    saveURLMapping()
    mutex.Unlock()

    // Return the short URL
    w.Write([]byte(fmt.Sprintf("Short URL: http://localhost:8080/%s", shortURL)))
}
  • Get Long URL: Extracts the original URL from the request query parameters.

  • Generate Short URL: Calls generateShortURL to create a unique hash.

  • Store Mapping: Uses a mutex lock to safely store the mapping and save it to a file.

6. Handle Redirection

The redirectHandler function takes the short URL from the request path, finds the corresponding long URL, and redirects the user.

func redirectHandler(w http.ResponseWriter, r *http.Request) {
    shortURL := r.URL.Path[1:] // Extract short URL from path

    mutex.Lock()
    longURL, exists := urlStore[shortURL]
    mutex.Unlock()

    if !exists {
        http.NotFound(w, r)
        return
    }

    http.Redirect(w, r, longURL, http.StatusFound)
}
  • Extract Short URL: Gets the short URL from the request path.

  • Find Long URL: Looks up the long URL in the urlStore.

  • Redirect: Uses http.Redirect to redirect the user to the original long URL.

7. Generate a Short URL

The generateShortURL function generates a unique short URL using the SHA-1 hashing algorithm.

func generateShortURL(longURL string) string {
    hash := sha1.New()
    hash.Write([]byte(longURL))
    return hex.EncodeToString(hash.Sum(nil))[:8] // Use the first 8 characters of the hash
}
  • SHA-1 Hashing: Creates a hash of the long URL.

  • Shorten Hash: Takes the first 8 characters to use as the short URL.

8. Save and Load URL Mappings

The saveURLMapping and loadURLMapping functions handle storing and retrieving URL mappings from a file to ensure data persistence.

func saveURLMapping() {
    data, err := json.Marshal(urlStore)
    if err != nil {
        log.Println("Error marshaling data:", err)
        return
    }

    err = ioutil.WriteFile("urls.json", data, 0644)
    if err != nil {
        log.Println("Error writing to file:", err)
    }
}

func loadURLMapping() {
    file, err := os.Open("urls.json")
    if err != nil {
        if os.IsNotExist(err) {
            return // If the file doesn't exist, nothing to load
        }
        log.Println("Error opening file:", err)
        return
    }
    defer file.Close()

    data, err := ioutil.ReadAll(file)
    if err != nil {
        log.Println("Error reading file:", err)
        return
    }

    err = json.Unmarshal(data, &urlStore)
    if err != nil {
        log.Println("Error unmarshaling data:", err)
    }
}
  • Save Mapping: Serializes the urlStore map to JSON and writes it to urls.json.

  • Load Mapping: Reads from urls.json and loads the data back into the urlStore map.

Running the Application

To run the code:

  1. Save the above code in main.go.

  2. Run the following command in your terminal:

go run main.go
  1. Your server will start on http://localhost:8080.
Testing the URL Shortener
  • Shorten a URL: Open your browser or use a tool like curl to test.

      curl "http://localhost:8080/shorten?url=https://example.com/long-url"
    
  • Redirect: Use the short URL provided by the server to check the redirection.

Conclusion

This tutorial shows how to build a basic URL shortener in Go. We covered how to handle HTTP requests, generate short URLs, manage concurrent access using a mutex, and persist data to a file. This example serves as a foundation for more advanced features, such as database integration, custom URL slugs, and user authentication.

0
Subscribe to my newsletter

Read articles from Sundaram Kumar Jha directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sundaram Kumar Jha
Sundaram Kumar Jha

I Like Building Cloud Native Stuff , the microservices, backends, distributed systemsand cloud native tools using Golang