Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
84: Building a URL Shortener Service in Go
A few years ago, I worked with a developer who built a "quick and dirty" URL shortener for a promotional campaign. He used a simple auto-incrementing integer from a SQL database as the short code. It worked perfectly for the first ten minutes. Then, a curious user noticed their link ended in /102, tried /101, and realized they could just iterate backwards to scrape every single private link the company had generated for its VIP clients. It was a classic mistake: predictability is the enemy of a good shortener.
To do this right, we need a way to map a long, cumbersome URL to a short, unique string that isn't easily guessable. While you could use a UUID, those are far too long to be "short." Instead, the industry standard is usually to take a unique numeric ID and encode it using Base62. This uses characters 0-9, a-z, and A-Z, giving us 62 possible characters for each slot in our string. It keeps the URLs tight while allowing for billions of combinations.
Encoding IDs into Base62
In Go, the cleanest way to handle this is to define our alphabet as a constant string and use a loop to convert our database ID into that alphabet. I usually prefer this over using a library because the logic is straightforward and gives you total control over the character set.
package main
import "fmt"
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func encode(number uint64) string {
if number == 0 {
return string(alphabet[0])
}
var result []byte
base := uint64(len(alphabet))
for number > 0 {
result = append(result, alphabet[number%base])
number /= base
}
// We reverse the slice because we calculated the remainders
// from least significant to most significant.
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = result[j], result[i]
}
return string(result)
}
Notice that I used a []byte slice here rather than concatenating strings. In Go, strings are immutable, so adding to a string in a loop creates a new string every single time. Using a slice is significantly more efficient for the garbage collector.
Wiring the Redirection Logic
Once we have our encoding logic, we need to wrap it in an HTTP server. You'll need two main endpoints: one to take a long URL and return a short one, and another to take that short code and redirect the user to the original destination. For this example, I'm using a simple map to store the URLs in memory. In a production environment, you'd obviously swap this for Redis or Postgres, but the interface remains the same.
package main
import (
"net/http"
"sync"
)
type Shortener struct {
mu sync.RWMutex
store map[string]string
idCounter uint64
}
func (s *Shortener) HandleShorten(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
if url == "" {
http.Error(w, "Missing url parameter", http.StatusBadRequest)
return
}
s.mu.Lock()
s.idCounter++
id := s.idCounter
s.mu.Unlock()
shortCode := encode(id)
s.mu.Lock()
s.store[shortCode] = url
s.mu.Unlock()
fmt.Fprintf(w, "Shortened URL: %s", shortCode)
}
func (s *Shortener) HandleRedirect(w http.ResponseWriter, r *http.Request) {
code := r.URL.Path[1:] // Strip the leading "/"
s.mu.RLock()
longURL, exists := s.store[code]
s.mu.RUnlock()
if !exists {
http.NotFound(w, r)
return
}
http.Redirect(w, r, longURL, http.StatusMovedPermanently)
}
I added a sync.RWMutex here because Go maps are not thread-safe. Since a URL shortener is read-heavy (way more people click links than create them), a RWMutex allows multiple readers to access the map simultaneously while ensuring that only one writer can modify the store at a time. If you forget this, your server will panic the moment two people try to shorten a link at the same millisecond.
📋 Practical Task
Build a Persistent Base62 URL Shortener with Custom Alias Support
Your task is to extend the provided shortener logic. Instead of relying solely on an auto-incrementing ID, modify the service so that users can optionally provide their own "custom alias" (e.g., /my-portfolio instead of /aB3x).
Requirements:
- Modify the
HandleShortenfunction to check for an optionalaliasquery parameter. - If an alias is provided, verify that it doesn't already exist in the store before saving it.
- If no alias is provided, fall back to the Base62 encoding logic.
- Implement a "Delete" endpoint that removes a short code from the store using a
DELETErequest. - Ensure all map access is protected by the
sync.RWMutexto prevent race conditions.
There are no comments for now.