Removes pkg/middleware. The Logger, Recoverer, and RealIP middlewares now live in the sibling lightmux-contrib module as the realip, requestlog, and recoverer packages, each exposing a single New(...) constructor. The Middleware type alias moves to pkg/router. The splinter dependency is dropped from go.mod; only errx remains. BREAKING CHANGE: consumers must replace pkg/middleware imports with the corresponding lightmux-contrib sub-packages. See README for the new usage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| pkg/router | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| lightmux.go | ||
| README.md | ||
| Taskfile.yml | ||
lightmux
A small, idiomatic wrapper around Go 1.22+ net/http.ServeMux that adds method-named convenience methods, composable groups, and per-route middleware — without abandoning the stdlib pattern syntax.
Installation
Requires Go 1.22 or later (uses http.ServeMux's method+pattern syntax and r.PathValue).
go get git.juancwu.dev/juancwu/lightmux@v0.1.0
Quick start
package main
import (
"fmt"
"log"
"net/http"
"strconv"
"git.juancwu.dev/juancwu/lightmux"
)
func main() {
mux := lightmux.New()
mux.Get("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello from lightmux")
})
mux.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
http.Error(w, "bad id", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "user %d\n", id)
})
api := mux.Group("/api")
api.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "pong")
})
log.Fatal(http.ListenAndServe(":8080", mux))
}
Groups
Group returns a sub-mux that shares the underlying ServeMux but carries its own prefix and middleware stack. Groups can be nested.
api := mux.Group("/api", authMiddleware)
api.Get("/users/{id}", getUser)
v1 := api.Group("/v1")
v1.Get("/ping", pong) // registered as "GET /api/v1/ping"
Middleware is snapshotted into the child at Group() time. Calling parent.Use(mw) after a Group() does not retroactively wrap the child's routes — register middleware before creating groups, or pass it to Group(prefix, mw...) explicitly.
Middleware
There are two ways to attach middleware: chain-style with Use, or per-route as a variadic tail.
mux := lightmux.New()
mux.Use(authMiddleware) // applies to every route registered after
mux.Get("/admin", adminHandler, requireAdmin) // requireAdmin only on this route
Order is outer → inner: Use-order first, then per-route mws. The outermost middleware runs first on the request and last on the response.
Middleware values are plain func(http.Handler) http.Handler, so any stdlib-compatible middleware works without an adapter.
An opinionated set of middlewares (request logging, panic recovery, real-IP resolution) lives in a sibling module — see lightmux-contrib:
go get git.juancwu.dev/juancwu/lightmux-contrib
import (
"git.juancwu.dev/juancwu/lightmux-contrib/realip"
"git.juancwu.dev/juancwu/lightmux-contrib/recoverer"
"git.juancwu.dev/juancwu/lightmux-contrib/requestlog"
)
mux.Use(recoverer.New(), realip.New(), requestlog.New(nil))
Path parameters
lightmux is a thin wrapper, so path parameters work the stdlib way:
mux.Get("/items/{name}", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, r.PathValue("name"))
})
Notes
mux.Get("/", h)registers"GET /", which is a stdlib subtree pattern — it matches every unmatched path. Use"/{$}"to match only the literal root.- Bad route registrations (invalid prefix, conflicting wildcards) panic at startup, matching stdlib
http.ServeMuxbehavior. Therecoverermiddleware in lightmux-contrib handles panics that occur inside request handlers. - Group prefixes apply to the path only — they never inject a host. Routes can still carry an explicit host via
Handle("GET host.com/path", h).