Compare commits

2 Commits
v0.9.0 ... main

Author SHA1 Message Date
32831d5a06 docs(httpmw): correct tier from 3 to 2 and fix logz tier ref
httpmw only depends on logz (Tier 1), placing it at Tier 2.
The previous docs incorrectly stated both the module tier (3) and
the logz tier (0).
2026-03-19 06:55:59 -06:00
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
2 changed files with 98 additions and 1 deletions

View File

@@ -11,7 +11,7 @@ authentication or identity logic lives here — see `httpauth-firebase` for that
## Tier & Dependencies
**Tier:** 3 (transport layer; depends on Tier 0 `logz`)
**Tier:** 2 (transport layer; depends on Tier 1 `logz`)
**Module:** `code.nochebuena.dev/go/httpmw`
**Direct imports:** `code.nochebuena.dev/go/logz`

97
README.md Normal file
View File

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