Files
httpmw/README.md
Rene Nochebuena 2298846280 docs(httpmw): add README.md
Document all four middleware functions (Recover, RequestID, RequestLogger, CORS),
the Logger interface, StatusRecorder, recommended middleware order, and install
instructions.
2026-03-18 19:35:13 -06:00

98 lines
2.7 KiB
Markdown

# 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`