lightmux/README.md
juancwu 22277186ae extract middlewares to lightmux-contrib
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>
2026-04-26 14:02:54 +00:00

3.4 KiB

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.ServeMux behavior. The recoverer middleware 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).