002 Project 2: URL Shortener API
002 Build a URL Shortener API
You will build a small HTTP service with two endpoints:
POST /shorten-> create short URLGET /:code-> redirect to original URL
Request flow
client -> POST /shorten -> store (code -> url) -> return short URL
client -> GET /abc123 -> lookup code -> 302 redirect
Full main.go
package main
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"sync"
)
type store struct {
mu sync.RWMutex
data map[string]string
}
type shortenRequest struct {
URL string `json:"url"`
}
type shortenResponse struct {
Code string `json:"code"`
URL string `json:"short_url"`
}
func newCode(n int) string {
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return strings.TrimRight(base64.RawURLEncoding.EncodeToString(b), "=")
}
func main() {
s := &store{data: make(map[string]string)}
mux := http.NewServeMux()
mux.HandleFunc("POST /shorten", func(w http.ResponseWriter, r *http.Request) {
var req shortenRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
u, err := url.ParseRequestURI(req.URL)
if err != nil || u.Scheme == "" || u.Host == "" {
http.Error(w, "invalid url", http.StatusBadRequest)
return
}
code := newCode(5)
s.mu.Lock()
s.data[code] = req.URL
s.mu.Unlock()
resp := shortenResponse{
Code: code,
URL: fmt.Sprintf("http://%s/%s", r.Host, code),
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
})
mux.HandleFunc("GET /{code}", func(w http.ResponseWriter, r *http.Request) {
code := r.PathValue("code")
s.mu.RLock()
longURL, ok := s.data[code]
s.mu.RUnlock()
if !ok {
http.NotFound(w, r)
return
}
http.Redirect(w, r, longURL, http.StatusFound)
})
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}Run and Test
go run .
curl -s -X POST localhost:8080/shorten \
-H 'content-type: application/json' \
-d '{"url":"https://go.dev/doc/"}'
curl -i localhost:8080/<code-from-response>What to Extend
- Persistent storage (BoltDB/Postgres).
- Expiry timestamps.
- Rate limiting middleware.
Step-by-Step Explanation
- Define request and response contracts first.
- Validate inbound input before doing any state changes.
- Keep handler logic short and move reusable logic into helper functions.
- Add timeouts and clear error paths.
- Return consistent responses and status codes.
Code Anatomy
- Handlers parse input, call domain logic, write response.
- Shared state uses synchronization where needed.
- Transport concerns stay separate from business rules.
Learning Goals
- Build reliable service endpoints in Go.
- Understand API ergonomics and operational safety.
- Prepare code structure for tests and persistence later.