From 2298846280f9f3591c854153e7f4bade7ef5fa88 Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Date: Wed, 18 Mar 2026 19:35:13 -0600 Subject: [PATCH] docs(httpmw): add README.md Document all four middleware functions (Recover, RequestID, RequestLogger, CORS), the Logger interface, StatusRecorder, recommended middleware order, and install instructions. --- README.md | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b093584 --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# httpmw + +`net/http` middleware for transport-layer concerns: panic recovery, CORS, request ID injection, and structured request logging. + +## Overview + +Four composable `func(http.Handler) http.Handler` middleware functions: + +| Middleware | Responsibility | +|---|---| +| `Recover()` | Catches panics; writes 500; captures stack trace | +| `RequestID(generator)` | Generates a unique ID; injects it via `logz.WithRequestID`; sets `X-Request-ID` header | +| `RequestLogger(logger)` | Logs method, path, status, latency, and request ID after each request | +| `CORS(origins)` | Sets CORS headers; short-circuits OPTIONS with 204 | + +No authentication or identity logic lives here — see `httpauth-firebase` for that. + +## Install + +``` +go get code.nochebuena.dev/go/httpmw +``` + +## Usage + +```go +// Recommended order — outermost middleware first +mux.Use(httpmw.Recover()) +mux.Use(httpmw.RequestID(uuid.NewString)) +mux.Use(httpmw.RequestLogger(logger)) +mux.Use(httpmw.CORS([]string{"https://example.com"})) +``` + +Pass `[]string{"*"}` to `CORS` to allow any origin (development only). + +## Middleware + +### Recover + +```go +mux.Use(httpmw.Recover()) +``` + +Wraps the handler in a `defer/recover`. On panic, writes `500 Internal Server Error` and captures `debug.Stack()`. No logger is required. + +### RequestID + +```go +mux.Use(httpmw.RequestID(uuid.NewString)) +``` + +Calls `generator()` on every request, stores the ID with `logz.WithRequestID`, and writes it to the `X-Request-ID` response header. Must run before `RequestLogger` so the ID is in context when the logger reads it. + +### RequestLogger + +```go +mux.Use(httpmw.RequestLogger(logger)) +``` + +Logs `method`, `path`, `status`, `latency`, and `request_id` after the inner handler returns. Uses `logger.Error` for 5xx responses and `logger.Info` for all others. + +### CORS + +```go +mux.Use(httpmw.CORS([]string{"https://app.example.com", "https://admin.example.com"})) +``` + +Sets `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, and `Access-Control-Allow-Headers` for matching origins. OPTIONS requests are short-circuited with 204 No Content. + +Allowed methods: `GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS` +Allowed headers: `Content-Type, Authorization, X-Request-ID` + +## Logger interface + +`RequestLogger` accepts any value satisfying the `Logger` interface — satisfied by `logz.Logger` via duck typing: + +```go +type Logger interface { + Info(msg string, args ...any) + Error(msg string, err error, args ...any) + With(args ...any) Logger +} +``` + +## StatusRecorder + +`StatusRecorder` is exported for use in custom middleware that needs to inspect the written status code: + +```go +rec := &httpmw.StatusRecorder{ResponseWriter: w, Status: http.StatusOK} +next.ServeHTTP(rec, r) +fmt.Println(rec.Status) // e.g. 404 +``` + +## Dependencies + +- `code.nochebuena.dev/go/logz`