1. go
  2. /web
  3. /routing

Web Routing in Go

Go provides several ways to handle URL routing in web applications. This guide covers both built-in routing capabilities and popular third-party routers.

Basic Routing

Standard HTTP Routing

package main

import (
    "fmt"
    "net/http"
)

func main() {
    // Basic route handlers
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/about", aboutHandler)
    http.HandleFunc("/users/", usersHandler)
    
    // Start server
    http.ListenAndServe(":8080", nil)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the home page!")
}

func aboutHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "About us")
}

func usersHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Users section")
}

Pattern Matching

func userHandler(w http.ResponseWriter, r *http.Request) {
    // Extract user ID from path
    path := r.URL.Path
    id := strings.TrimPrefix(path, "/users/")
    
    if id == "" {
        // List users
        fmt.Fprintf(w, "List of users")
        return
    }
    
    // Show specific user
    fmt.Fprintf(w, "User: %s", id)
}

func main() {
    http.HandleFunc("/users/", userHandler)
}

Advanced Routing

Custom Router

type Router struct {
    routes map[string]http.HandlerFunc
}

func NewRouter() *Router {
    return &Router{
        routes: make(map[string]http.HandlerFunc),
    }
}

func (r *Router) Handle(pattern string, handler http.HandlerFunc) {
    r.routes[pattern] = handler
}

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    for pattern, handler := range r.routes {
        if matched, _ := path.Match(pattern, req.URL.Path); matched {
            handler(w, req)
            return
        }
    }
    http.NotFound(w, req)
}

Method-Based Routing

type Route struct {
    Method  string
    Pattern string
    Handler http.HandlerFunc
}

type Router struct {
    routes []Route
}

func (r *Router) HandleFunc(method, pattern string, handler http.HandlerFunc) {
    r.routes = append(r.routes, Route{
        Method:  method,
        Pattern: pattern,
        Handler: handler,
    })
}

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    for _, route := range r.routes {
        if route.Method == req.Method && route.Pattern == req.URL.Path {
            route.Handler(w, req)
            return
        }
    }
    http.NotFound(w, req)
}

Gorilla Mux

import "github.com/gorilla/mux"

func main() {
    r := mux.NewRouter()
    
    // Basic routes
    r.HandleFunc("/", HomeHandler)
    r.HandleFunc("/products", ProductsHandler)
    
    // Route with variables
    r.HandleFunc("/product/{id}", ProductHandler)
    
    // Route with regex
    r.HandleFunc("/articles/{category}/{id:[0-9]+}",
        ArticleHandler)
    
    // Method-specific routes
    r.HandleFunc("/api/posts", PostsHandler).
        Methods("GET")
    r.HandleFunc("/api/posts", CreatePostHandler).
        Methods("POST")
    
    // Subrouter
    s := r.PathPrefix("/api/v1").Subrouter()
    s.HandleFunc("/users", UsersHandler)
    
    http.ListenAndServe(":8080", r)
}

Chi Router

import "github.com/go-chi/chi/v5"

func main() {
    r := chi.NewRouter()
    
    // Middleware
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    
    // Routes
    r.Get("/", HomeHandler)
    r.Get("/users", UsersHandler)
    
    // RESTful routes
    r.Route("/articles", func(r chi.Router) {
        r.Get("/", ListArticles)
        r.Post("/", CreateArticle)
        
        r.Route("/{id}", func(r chi.Router) {
            r.Get("/", GetArticle)
            r.Put("/", UpdateArticle)
            r.Delete("/", DeleteArticle)
        })
    })
    
    http.ListenAndServe(":8080", r)
}

Best Practices

1. Route Organization

type API struct {
    router *mux.Router
    db     *sql.DB
}

func NewAPI(db *sql.DB) *API {
    api := &API{
        router: mux.NewRouter(),
        db:     db,
    }
    
    api.routes()
    return api
}

func (a *API) routes() {
    // Group related routes
    a.router.HandleFunc("/health", a.healthCheck)
    
    // API routes
    api := a.router.PathPrefix("/api/v1").Subrouter()
    
    // User routes
    users := api.PathPrefix("/users").Subrouter()
    users.HandleFunc("", a.listUsers).Methods("GET")
    users.HandleFunc("/{id}", a.getUser).Methods("GET")
    
    // Product routes
    products := api.PathPrefix("/products").Subrouter()
    products.HandleFunc("", a.listProducts).Methods("GET")
    products.HandleFunc("/{id}", a.getProduct).Methods("GET")
}

2. Route Parameters

func (a *API) getUser(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id := vars["id"]
    
    // Validate ID
    if !isValidID(id) {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }
    
    // Get user from database
    user, err := a.db.GetUser(id)
    if err != nil {
        if err == sql.ErrNoRows {
            http.Error(w, "User not found", http.StatusNotFound)
            return
        }
        http.Error(w, "Server error", http.StatusInternalServerError)
        return
    }
    
    // Return user
    json.NewEncoder(w).Encode(user)
}

3. Middleware Integration

func (a *API) authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        
        // Validate token
        user, err := a.validateToken(token)
        if err != nil {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        
        // Add user to context
        ctx := context.WithValue(r.Context(), "user", user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func (a *API) routes() {
    // Protected routes
    protected := a.router.PathPrefix("/api").Subrouter()
    protected.Use(a.authMiddleware)
    
    protected.HandleFunc("/profile", a.getProfile)
    protected.HandleFunc("/settings", a.getSettings)
}

Common Patterns

1. RESTful Routing

type ResourceHandler struct {
    db *sql.DB
}

func (h *ResourceHandler) Register(r *mux.Router) {
    r.HandleFunc("", h.List).Methods("GET")
    r.HandleFunc("", h.Create).Methods("POST")
    r.HandleFunc("/{id}", h.Get).Methods("GET")
    r.HandleFunc("/{id}", h.Update).Methods("PUT")
    r.HandleFunc("/{id}", h.Delete).Methods("DELETE")
}

func main() {
    r := mux.NewRouter()
    
    // Register resource handlers
    users := &ResourceHandler{db: db}
    r.PathPrefix("/users").Handler(users)
    
    products := &ResourceHandler{db: db}
    r.PathPrefix("/products").Handler(products)
}

2. Versioned API Routes

func NewAPIRouter() *mux.Router {
    r := mux.NewRouter()
    
    // API versions
    v1 := r.PathPrefix("/api/v1").Subrouter()
    registerV1Routes(v1)
    
    v2 := r.PathPrefix("/api/v2").Subrouter()
    registerV2Routes(v2)
    
    return r
}

func registerV1Routes(r *mux.Router) {
    r.HandleFunc("/users", v1.ListUsers).Methods("GET")
    r.HandleFunc("/users/{id}", v1.GetUser).Methods("GET")
}

func registerV2Routes(r *mux.Router) {
    r.HandleFunc("/users", v2.ListUsers).Methods("GET")
    r.HandleFunc("/users/{id}", v2.GetUser).Methods("GET")
}

Next Steps