mux

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 20, 2025 License: MIT Imports: 15 Imported by: 0

README

Mux - A Lightweight HTTP Router for Go

Go Version License

Mux is a simple, lightweight HTTP router for Go that wraps around the standard http.ServeMux to provide additional functionality and a more ergonomic API for building web applications and APIs.

Features

  • 🚀 Built on top of Go's standard http.ServeMux (Go 1.22+ routing enhancements)
  • 🎯 HTTP method-specific routing (GET, POST, PUT, DELETE, PATCH, etc.)
  • 🔌 Flexible middleware support with stackable composition
  • 📦 Route grouping for organization and shared middleware
  • 🎨 RESTful resource routing with collection and member routes
  • 🔗 URL parameter extraction using Go's standard path values
  • 🛡️ Graceful shutdown support with signal handling
  • 📋 Route listing and debugging
  • ⚡ Zero external dependencies (only Go standard library)
  • 🪶 Minimal overhead and excellent performance

Requirements

  • Go 1.25 or higher

Installation

go get code.patial.tech/go/mux

Quick Start

package main

import (
    "fmt"
    "net/http"
    "code.patial.tech/go/mux"
)

func main() {
    // Create a new router
    m := mux.New()

    // Define routes
    m.GET("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello, World!")
    })

    m.GET("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
        id := r.PathValue("id")
        fmt.Fprintf(w, "User ID: %s", id)
    })

    // Start server with graceful shutdown
    m.Serve(func(srv *http.Server) error {
        srv.Addr = ":8080"
        return srv.ListenAndServe()
    })
}

Table of Contents

Basic Routing

Mux supports all standard HTTP methods:

m := mux.New()

// HTTP method routes
m.GET("/users", listUsers)
m.POST("/users", createUser)
m.PUT("/users/{id}", updateUser)
m.PATCH("/users/{id}", partialUpdateUser)
m.DELETE("/users/{id}", deleteUser)
m.HEAD("/users", headUsers)
m.OPTIONS("/users", optionsUsers)
m.CONNECT("/proxy", connectProxy)
m.TRACE("/debug", traceDebug)

URL Parameters

Extract URL parameters using Go's standard r.PathValue():

m.GET("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
    userID := r.PathValue("id")
    fmt.Fprintf(w, "Fetching user: %s", userID)
})

m.GET("/posts/{year}/{month}/{slug}", func(w http.ResponseWriter, r *http.Request) {
    year := r.PathValue("year")
    month := r.PathValue("month")
    slug := r.PathValue("slug")
    // ... handle request
})

Middleware

Middleware functions follow the standard func(http.Handler) http.Handler signature, making them compatible with most Go middleware libraries.

Global Middleware

Apply middleware to all routes:

// Logging middleware
func logger(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        slog.Info("request", "method", r.Method, "path", r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

// Authentication middleware
func auth(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

m := mux.New()
m.Use(logger)
m.Use(auth)

// All routes will use logger and auth middleware
m.GET("/protected", protectedHandler)

Works with any middleware following the standard signature:

import (
    "github.com/gorilla/handlers"
    chimiddleware "github.com/go-chi/chi/v5/middleware"
)

m.Use(handlers.CompressHandler)
m.Use(chimiddleware.RealIP)
m.Use(chimiddleware.Recoverer)

Route Groups

Organize routes and apply middleware to specific groups:

m := mux.New()

// Public routes
m.GET("/", homeHandler)
m.GET("/about", aboutHandler)

// API routes with shared middleware
m.Group(func(api *mux.Mux) {
    api.Use(jsonMiddleware)
    api.Use(authMiddleware)
    
    api.GET("/api/users", listUsersAPI)
    api.POST("/api/users", createUserAPI)
    api.DELETE("/api/users/{id}", deleteUserAPI)
})

// Admin routes with different middleware
m.Group(func(admin *mux.Mux) {
    admin.Use(adminAuthMiddleware)
    admin.Use(auditLogMiddleware)
    
    admin.GET("/admin/dashboard", dashboardHandler)
    admin.GET("/admin/users", adminUsersHandler)
})

RESTful Resources

Define RESTful resources with conventional routing:

m.Resource("/posts", func(res *mux.Resource) {
    // Standard RESTful routes
    res.Index(listPosts)       // GET    /posts
    res.CreateView(newPostForm) // GET    /posts/create
    res.Create(createPost)     // POST   /posts
    res.View(showPost)         // GET    /posts/{id}
    res.Update(updatePost)     // PUT    /posts/{id}
    res.UpdatePartial(patchPost) // PATCH  /posts/{id}
    res.Delete(deletePost)     // DELETE /posts/{id}
})
Custom Resource Routes

Add custom routes at collection or member level:

m.Resource("/posts", func(res *mux.Resource) {
    // Standard routes
    res.Index(listPosts)
    res.View(showPost)
    
    // Collection-level custom routes (on /posts/...)
    res.POST("/search", searchPosts)           // POST /posts/search
    res.GET("/archived", archivedPosts)        // GET  /posts/archived
    res.GET("/trending", trendingPosts)        // GET  /posts/trending
    
    // Member-level custom routes (on /posts/{id}/...)
    res.MemberPOST("/publish", publishPost)    // POST /posts/{id}/publish
    res.MemberPOST("/archive", archivePost)    // POST /posts/{id}/archive
    res.MemberGET("/comments", getComments)    // GET  /posts/{id}/comments
    res.MemberDELETE("/cache", clearCache)     // DELETE /posts/{id}/cache
})
Collection vs Member Routes
  • Collection routes (POST, GET, PUT, PATCH, DELETE): Operate on /pattern/action

    • Example: res.POST("/search", handler)POST /posts/search
  • Member routes (MemberPOST, MemberGET, MemberPUT, MemberPATCH, MemberDELETE): Operate on /pattern/{id}/action

    • Example: res.MemberPOST("/publish", handler)POST /posts/{id}/publish
Resource Middleware

Apply middleware to all resource routes:

m.Resource("/posts", func(res *mux.Resource) {
    // Middleware for all routes in this resource
    res.Use(postAuthMiddleware)
    res.Use(postValidationMiddleware)
    
    res.Index(listPosts)
    res.Create(createPost)
    // ... other routes
}, resourceSpecificMiddleware) // Can also pass middleware as arguments

Inline Middleware

Apply middleware to specific routes without affecting others:

m := mux.New()

// Route without middleware
m.GET("/public", publicHandler)

// Route with inline middleware
m.With(authMiddleware, rateLimitMiddleware).
    GET("/protected", protectedHandler)

// Another route with different middleware
m.With(adminMiddleware).
    POST("/admin/action", adminActionHandler)

Graceful Shutdown

Built-in graceful shutdown with signal handling:

m := mux.New()

// Define routes...
m.GET("/", homeHandler)

// Serve with graceful shutdown
m.Serve(func(srv *http.Server) error {
    srv.Addr = ":8080"
    srv.ReadTimeout = 10 * time.Second
    srv.WriteTimeout = 10 * time.Second
    
    slog.Info("Server starting", "addr", srv.Addr)
    return srv.ListenAndServe()
})

Features:

  • Listens for SIGINT and SIGTERM signals
  • Drains existing connections (10 second grace period)
  • Propagates shutdown signal to handlers via m.IsShuttingDown
  • Automatic OPTIONS handler for all routes
  • Hard shutdown after grace period to prevent hanging
Checking Shutdown Status
m.GET("/health", func(w http.ResponseWriter, r *http.Request) {
    if m.IsShuttingDown.Load() {
        w.WriteHeader(http.StatusServiceUnavailable)
        w.Write([]byte("Server is shutting down"))
        return
    }
    w.Write([]byte("OK"))
})

Route Debugging

List all registered routes:

// Print routes to stdout
m.PrintRoutes(os.Stdout)

// Get routes as slice
routes := m.RouteList()
for _, route := range routes {
    fmt.Println(route)
}

// Expose routes via HTTP endpoint
m.GET("/debug/routes", func(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    m.PrintRoutes(w)
})

Output example:

GET /
GET /users
POST /users
GET /users/{id}
PUT /users/{id}
DELETE /users/{id}
GET /posts
POST /posts/search
GET /posts/{id}
POST /posts/{id}/publish

Complete Example

package main

import (
    "fmt"
    "log/slog"
    "net/http"
    "os"
    "time"
    
    "code.patial.tech/go/mux"
)

func main() {
    m := mux.New()
    
    // Global middleware
    m.Use(loggingMiddleware)
    m.Use(recoveryMiddleware)
    
    // Public routes
    m.GET("/", homeHandler)
    m.GET("/about", aboutHandler)
    
    // API group
    m.Group(func(api *mux.Mux) {
        api.Use(jsonMiddleware)
        
        // Users resource
        api.Resource("/users", func(res *mux.Resource) {
            res.Index(listUsers)
            res.Create(createUser)
            res.View(showUser)
            res.Update(updateUser)
            res.Delete(deleteUser)
            
            // Custom user actions
            res.POST("/search", searchUsers)
            res.MemberPOST("/activate", activateUser)
            res.MemberGET("/posts", getUserPosts)
        })
        
        // Posts resource
        api.Resource("/posts", func(res *mux.Resource) {
            res.Index(listPosts)
            res.Create(createPost)
            res.View(showPost)
            
            res.MemberPOST("/publish", publishPost)
            res.MemberGET("/comments", getPostComments)
        })
    })
    
    // Debug route
    m.GET("/debug/routes", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "text/plain")
        m.PrintRoutes(w)
    })
    
    // Start server
    m.Serve(func(srv *http.Server) error {
        srv.Addr = ":8080"
        srv.ReadTimeout = 30 * time.Second
        srv.WriteTimeout = 30 * time.Second
        
        slog.Info("Server listening", "addr", srv.Addr)
        return srv.ListenAndServe()
    })
}

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        slog.Info("request", 
            "method", r.Method,
            "path", r.URL.Path,
            "duration", time.Since(start))
    })
}

func recoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                slog.Error("panic recovered", "error", err)
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

func jsonMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        next.ServeHTTP(w, r)
    })
}

// Handler implementations
func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Welcome Home!")
}

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

func listUsers(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, `{"users": []}`)
}

func createUser(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, `{"message": "User created"}`)
}

func showUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, `{"id": "%s"}`, id)
}

func updateUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, `{"message": "User %s updated"}`, id)
}

func deleteUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, `{"message": "User %s deleted"}`, id)
}

func searchUsers(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, `{"results": []}`)
}

func activateUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, `{"message": "User %s activated"}`, id)
}

func getUserPosts(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, `{"user_id": "%s", "posts": []}`, id)
}

func listPosts(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, `{"posts": []}`)
}

func createPost(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, `{"message": "Post created"}`)
}

func showPost(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, `{"id": "%s"}`, id)
}

func publishPost(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, `{"message": "Post %s published"}`, id)
}

func getPostComments(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, `{"post_id": "%s", "comments": []}`, id)
}

Custom 404 Handler

Handle 404 errors with a catch-all route:

m.GET("/", func(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.Error(w, "404 - Page Not Found", http.StatusNotFound)
        return
    }
    // Handle root path
    fmt.Fprint(w, "Home Page")
})

Testing

Testing routes is straightforward using Go's httptest package:

func TestHomeHandler(t *testing.T) {
    m := mux.New()
    m.GET("/", homeHandler)
    
    req := httptest.NewRequest(http.MethodGet, "/", nil)
    rec := httptest.NewRecorder()
    
    m.ServeHTTP(rec, req)
    
    if rec.Code != http.StatusOK {
        t.Errorf("expected status 200, got %d", rec.Code)
    }
}

Performance

Mux has minimal overhead since it wraps Go's standard http.ServeMux:

  • Zero heap allocations for simple routes
  • Efficient middleware chaining using composition
  • Fast pattern matching powered by Go's stdlib
  • No reflection or runtime code generation

Contributing

Contributions are welcome! Please feel free to submit issues, fork the repository, and create pull requests.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Credits

Built with ❤️ using Go's excellent standard library.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Mux added in v0.5.0

type Mux struct {
	IsShuttingDown atomic.Bool
	// contains filtered or unexported fields
}

Mux is a wrapper around the go's standard http.ServeMux. It's a lean wrapper with methods to make routing easier

func New added in v0.5.0

func New() *Mux

func (*Mux) CONNECT added in v0.5.0

func (m *Mux) CONNECT(pattern string, h http.HandlerFunc)

CONNECT method route

func (*Mux) DELETE added in v0.5.0

func (m *Mux) DELETE(pattern string, h http.HandlerFunc, mw ...func(http.Handler) http.Handler)

DELETE method route

func (*Mux) GET added in v0.5.0

func (m *Mux) GET(pattern string, h http.HandlerFunc, mw ...func(http.Handler) http.Handler)

GET method route

func (*Mux) Group added in v0.5.0

func (m *Mux) Group(fn func(grp *Mux))

Group adds a new inline-Router along the current routing path, with a fresh middleware stack for the inline-Router.

func (*Mux) HEAD added in v0.5.0

func (m *Mux) HEAD(pattern string, h http.HandlerFunc, mw ...func(http.Handler) http.Handler)

HEAD method route

func (*Mux) HttpServeMux added in v0.5.0

func (m *Mux) HttpServeMux() *http.ServeMux

HttpServeMux DO NOT USE it for routing, exposed only for edge cases.

func (*Mux) OPTIONS added in v0.5.0

func (m *Mux) OPTIONS(pattern string, h http.HandlerFunc)

OPTIONS method route

func (*Mux) PATCH added in v0.5.0

func (m *Mux) PATCH(pattern string, h http.HandlerFunc, mw ...func(http.Handler) http.Handler)

PATCH method route

func (*Mux) POST added in v0.5.0

func (m *Mux) POST(pattern string, h http.HandlerFunc, mw ...func(http.Handler) http.Handler)

POST method route

func (*Mux) PUT added in v0.5.0

func (m *Mux) PUT(pattern string, h http.HandlerFunc, mw ...func(http.Handler) http.Handler)

PUT method route

func (*Mux) PrintRoutes added in v0.5.0

func (m *Mux) PrintRoutes(w io.Writer)

func (*Mux) Resource added in v0.5.0

func (m *Mux) Resource(pattern string, fn func(res *Resource), mw ...func(http.Handler) http.Handler)

Resource routes mapping by using HTTP verbs

  • GET /pattern view all resources
  • GET /pattern/create new resource view
  • POST /pattern create a new resource
  • GET /pattern/{id} view a resource
  • PUT /pattern/{id} update a resource
  • PATCH /pattern/{id} partial update a resource
  • DELETE /resource/{id} delete a resource

func (*Mux) RouteList added in v0.5.0

func (m *Mux) RouteList() []string

func (*Mux) Serve added in v0.5.0

func (m *Mux) Serve(cb ServeCB)

Serve with graceful shutdown

func (*Mux) ServeHTTP added in v0.5.0

func (m *Mux) ServeHTTP(w http.ResponseWriter, req *http.Request)

func (*Mux) TRACE added in v0.5.0

func (m *Mux) TRACE(pattern string, h http.HandlerFunc)

TRACE method route

func (*Mux) Use added in v0.5.0

func (m *Mux) Use(h ...func(http.Handler) http.Handler)

Use will register middleware(s) with router stack

func (*Mux) With added in v0.5.0

func (m *Mux) With(mw ...func(http.Handler) http.Handler) *Mux

With adds inline middlewares for an endpoint handler.

type Resource

type Resource struct {
	// contains filtered or unexported fields
}

func (*Resource) Create

func (res *Resource) Create(h http.HandlerFunc)

Create a new resource

POST /pattern/create

func (*Resource) CreateView added in v0.5.0

func (res *Resource) CreateView(h http.HandlerFunc)

CreateView new resource

GET /pattern/create

func (*Resource) DELETE added in v1.0.0

func (res *Resource) DELETE(pattern string, h http.HandlerFunc)

DELETE registers a custom DELETE route at collection level

DELETE /pattern/route

func (*Resource) Delete added in v0.5.0

func (res *Resource) Delete(h http.HandlerFunc)

Delete a resource

DELETE /pattern/{id}

func (*Resource) GET added in v1.0.0

func (res *Resource) GET(pattern string, h http.HandlerFunc)

GET registers a custom GET route at collection level

GET /pattern/route

func (*Resource) Index

func (res *Resource) Index(h http.HandlerFunc)

Index of all resource.

GET /pattern

func (*Resource) MemberDELETE added in v1.0.0

func (res *Resource) MemberDELETE(pattern string, h http.HandlerFunc)

MemberDELETE registers a custom DELETE route at member level

DELETE /pattern/{id}/route

func (*Resource) MemberGET added in v1.0.0

func (res *Resource) MemberGET(pattern string, h http.HandlerFunc)

MemberGET registers a custom GET route at member level

GET /pattern/{id}/route

func (*Resource) MemberPATCH added in v1.0.0

func (res *Resource) MemberPATCH(pattern string, h http.HandlerFunc)

MemberPATCH registers a custom PATCH route at member level

PATCH /pattern/{id}/route

func (*Resource) MemberPOST added in v1.0.0

func (res *Resource) MemberPOST(pattern string, h http.HandlerFunc)

MemberPOST registers a custom POST route at member level

POST /pattern/{id}/route

func (*Resource) MemberPUT added in v1.0.0

func (res *Resource) MemberPUT(pattern string, h http.HandlerFunc)

MemberPUT registers a custom PUT route at member level

PUT /pattern/{id}/route

func (*Resource) PATCH added in v1.0.0

func (res *Resource) PATCH(pattern string, h http.HandlerFunc)

PATCH registers a custom PATCH route at collection level

PATCH /pattern/route

func (*Resource) POST added in v1.0.0

func (res *Resource) POST(pattern string, h http.HandlerFunc)

POST registers a custom POST route at collection level

POST /pattern/route

func (*Resource) PUT added in v1.0.0

func (res *Resource) PUT(pattern string, h http.HandlerFunc)

PUT registers a custom PUT route at collection level

PUT /pattern/route

func (*Resource) Update

func (res *Resource) Update(h http.HandlerFunc)

Update a resource

PUT /pattern/{id}

func (*Resource) UpdatePartial added in v0.5.0

func (res *Resource) UpdatePartial(h http.HandlerFunc)

UpdatePartial resource info PATCH /pattern/{id}

func (*Resource) Use

func (res *Resource) Use(middlewares ...func(http.Handler) http.Handler)

Use will register middleware(s) on Router stack.

func (*Resource) View added in v0.5.0

func (res *Resource) View(h http.HandlerFunc)

View a resource

GET /pattern/{id}

type RouteList added in v0.5.0

type RouteList struct {
	// contains filtered or unexported fields
}

func (*RouteList) Add added in v0.5.0

func (s *RouteList) Add(item string)

func (*RouteList) All added in v0.5.0

func (s *RouteList) All() []string

func (*RouteList) Get added in v0.5.0

func (s *RouteList) Get(index int) (string, error)

type ServeCB

type ServeCB func(srv *http.Server) error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL