DEV Community

Cover image for Building a URL Shortener in Go
luthfisauqi17
luthfisauqi17

Posted on • Edited on

Building a URL Shortener in Go

⚠️ Important Note: There have been several revisions to this article, which I have listed at the end. Please make sure to read them before following this tutorial. Thanks!

Have you ever wondered how Bit.ly or TinyURL work? Today, we're building our URL shortener in Golang!

By the end of this tutorial, you'll have a fully working URL shortener that generates short links and redirects users. Let’s get started!

Before we dive into coding, let's understand how a URL shortener works:

  1. The user enters a long URL
  2. We generate a short code
  3. Save it in a memory or database
  4. When someone visits the short link, we redirect them

Step 1: Project Setup

First, create a new project and initialize Go modules.

mkdir go-url-shortener && cd go-url-shortener
go mod init github.com/yourusername/go-url-shortener
go get github.com/gin-gonic/gin
Enter fullscreen mode Exit fullscreen mode

Now, open main.go and set up a simple Gin server.

package main

import (
    "crypto/rand"
    "encoding/base64"
    "github.com/gin-gonic/gin"
    "net/http"
)

// Map to store short URLs -> original URLs
var urlStore = make(map[string]string)

func main() {
    r := gin.Default()
    r.POST("/shorten", shortenURL)
    r.GET("/:short", redirectURL)

    r.Run(":8080") // Run on port 8080
}
Enter fullscreen mode Exit fullscreen mode

This creates a basic Gin server. Now let’s add URL shortening!

Step 2: Generate Short URLs

Now, we need a function to generate a short random URL.

func generateShortURL() string {
    b := make([]byte, 6)
    rand.Read(b)
    return base64.URLEncoding.EncodeToString(b)[:6]
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Shorten URL API

Next, let’s create the /shorten endpoint that takes a long URL and returns a short one.

func shortenURL(c *gin.Context) {
    var req struct {
        OriginalURL string `json:"original_url"`
    }
    if err := c.BindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
        return
    }

    shortCode := generateShortURL()

    urlStore[shortCode] = req.OriginalURL

    c.JSON(http.StatusOK, gin.H{
        "short_url": "http://localhost:8080/" + shortCode,
    })
}

Enter fullscreen mode Exit fullscreen mode

This stores the original URL in a map and returns a short URL.
Now, let’s handle redirection!

Step 4: Redirect Short URLs

We need an endpoint that looks up the short URL and redirects users.

func redirectURL(c *gin.Context) {
    shortCode := c.Param("short")

    originalURL, exists := urlStore[shortCode]

    if !exists {
        c.JSON(http.StatusNotFound, gin.H{"error": "URL not found"})
        return
    }

    c.Redirect(http.StatusFound, originalURL)
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Testing the API

Let’s test this API using cURL!
Run the application by typing.

go run .
Enter fullscreen mode Exit fullscreen mode

Shorten a URL

Request:

curl -X POST http://localhost:8080/shorten -H "Content-Type: application/json" -d '{"original_url": "https://google.com"}'
Enter fullscreen mode Exit fullscreen mode

Response:

{
    "short_url": "http://localhost:8080/abc123"
}
Enter fullscreen mode Exit fullscreen mode

Redirect (Visit the short URL)

curl -v http://localhost:8080/abc123
Enter fullscreen mode Exit fullscreen mode

Full code: https://github.com/luthfisauqi17/go-url-shortner


There you go, that is how you build a URL Shortener using Golang. Thank you for reading, and have a nice day!


⚠️ Revision

  • I just realized that sync.Mutex is needed to safeguard golang's map during concurrent writes. The final code including this revision is already added to the GitHub code.
  • Standard Base64 encoding can produce characters like +, /, and =, which aren't always URL-safe. I’ve updated the code to use base64.RawURLEncoding, which replaces those characters and removes padding. The final code including this revision is already added to the GitHub code.

Top comments (5)

Collapse
 
ray_kudjie_a5c193640359ce profile image
ray kudjie

Thanks for the article,

I just wanted to point out that multiple concurrent writes to a map are never safe without proper synchronization in Go.
Your application will handle concurrent traffic therefore you have to use a mutex( sync.Mutex) with the map that stores the URLs

Collapse
 
luthfisauqi17 profile image
luthfisauqi17

Hi, thanks for pointing that out!

Yes, I just realized that mutex(sync.Mutex) is needed to safeguard golang's map during concurrent writes. I add the revision to this article.

Collapse
 
vearutop profile image
Viacheslav Poturaev

I understand this is a toy project for sake of learning basics of Go.

Here are a few considerations that would make sense for production:

  • random allows duplicate long urls being shortened with different tokens, deterministic shortening with hashing can help to fight duplicates,
  • in space of 6 chars collisions are realistically possible, they should be handled to avoid corruption of existing links,
  • short links are usually targeted at people (not bots), and in some cases those links are typed by hand, alphabet that avoids similarly looking chars (e.g. 0 vs O, I vs l) helps.
Collapse
 
shenoudafawzy profile image
Shenouda Fawzy

But won't using Base64 encoding could possibly encode the random bytes into something like '?' or '/' which are valid Base64 symbols however, these are used symbols in the URL, and would invalidate that URL?

Collapse
 
luthfisauqi17 profile image
luthfisauqi17

You're absolutely right! Standard Base64 encoding can produce characters like +, /, and =, which aren't always URL-safe. I’ve updated the code to use base64.RawURLEncoding, which replaces those characters and removes padding. Thanks for pointing it out!