DEV Community

Cover image for Building a URL Shortener in Go
luthfisauqi17
luthfisauqi17

Posted on • Edited on

Building a URL Shortener in Go

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.

Top comments (2)

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.