From 24d8590b563382009e7e07d5fe4d5d7fb2a464de Mon Sep 17 00:00:00 2001 From: juancwu Date: Sun, 26 Apr 2026 01:56:25 +0000 Subject: [PATCH] delete runable example and update readme --- README.md | 109 ++++++++++++++++++++++++++++++++++++++++- examples/basic/main.go | 46 ----------------- 2 files changed, 108 insertions(+), 47 deletions(-) delete mode 100644 examples/basic/main.go diff --git a/README.md b/README.md index 7af287e..de35e01 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,110 @@ # lightmux -Simple router/mux for Go projects. \ No newline at end of file +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`). + +```sh +go get git.juancwu.dev/juancwu/lightmux +``` + +## Quick start + +```go +package main + +import ( + "fmt" + "log" + "net/http" + "strconv" + + "git.juancwu.dev/juancwu/lightmux" + "git.juancwu.dev/juancwu/lightmux/pkg/middleware" +) + +func main() { + mux := lightmux.New() + mux.Use(middleware.Recoverer, middleware.Logger) + + 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. + +```go +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. + +```go +mux := lightmux.New() +mux.Use(middleware.Recoverer, middleware.Logger) // 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. + +## Built-in middleware + +The `pkg/middleware` package ships: + +- **`Logger`** — emits a structured `http.request` record (method, path, status, duration) via [splinter](https://git.juancwu.dev/juancwu/splinter)'s default logger. +- **`LoggerWith(*splinter.Logger)`** — same, but routes records through the supplied splinter logger instead of the default. +- **`Recoverer`** — catches panics inside handlers, wraps the value with [errx](https://git.juancwu.dev/juancwu/errx) under op `middleware.Recoverer`, logs it with the stack, and writes a 500 response. + +```go +custom := splinter.New(splinter.WithStream(...)) +mux.Use(middleware.Recoverer, middleware.LoggerWith(custom)) +``` + +## Path parameters + +lightmux is a thin wrapper, so path parameters work the stdlib way: + +```go +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. `Recoverer` 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)`. diff --git a/examples/basic/main.go b/examples/basic/main.go deleted file mode 100644 index 4991fec..0000000 --- a/examples/basic/main.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - "strconv" - - "git.juancwu.dev/juancwu/lightmux" - "git.juancwu.dev/juancwu/lightmux/pkg/middleware" -) - -func main() { - mux := lightmux.New() - mux.Use(middleware.Recoverer, middleware.Logger) - - 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) - }) - - mux.Get("/panic", func(w http.ResponseWriter, r *http.Request) { - panic("demonstrating Recoverer") - }) - - api := mux.Group("/api") - api.Get("/ping", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "pong") - }) - - v1 := api.Group("/v1") - v1.Get("/items/{name}", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "item: %s\n", r.PathValue("name")) - }) - - log.Println("listening on :8080") - log.Fatal(http.ListenAndServe(":8080", mux)) -}