Middleware Primer
Middleware Primer
Section titled “Middleware Primer”Open this page after Stable Roots when the change still belongs to the stable surface, but the real question has narrowed to transport-only HTTP middleware.
middleware owns request/response wrappers, ordering-sensitive transport behavior, and request instrumentation that stays independent from business policy.
app.Use(requestid.Middleware())app.Use(recovery.Recovery(logger))app.Use(accesslog.Middleware(logger))Start here when
Section titled “Start here when”- you are adding or adjusting
func(http.Handler) http.Handlerbehavior - the work is about ordering-sensitive HTTP transport concerns
- the change instruments requests without owning tenant or product policy
Do not start here when
Section titled “Do not start here when”- the behavior is business validation, tenant resolution, or tenant quota policy
- the task is API version negotiation or protocol transformation
- the work is really app construction rather than a transport wrapper
First files to read in the current repository
Section titled “First files to read in the current repository”middleware/module.yamldocs/reference/canonical-style-guide.md- the target package under
middleware/*
Concrete ownership examples
Section titled “Concrete ownership examples”Keep it in middleware when the work is about | Move out when the work becomes |
|---|---|
| request IDs, tracing hooks, access logging, and HTTP metrics | broader observability adapter and export wiring in x/observability |
auth and security-header HTTP adapters on top of security/* primitives | tenant policy and tenant resolution in x/tenant |
| thin rate-limit adapters over stable abuse-guard primitives | feature catalogs of limiter implementations or capability-specific quota behavior |
| explicit constructor-based HTTP wrappers | API version negotiation in x/rest/versioning or protocol adaptation in x/gateway/* |
Attach middleware to an app
Section titled “Attach middleware to an app”app.Use(mw1, mw2, mw3) // runs in registration order, outermost firstUse must be called before app.Prepare().
Built-in middleware catalog
Section titled “Built-in middleware catalog”| Package | Constructor | What it does |
|---|---|---|
middleware/requestid | requestid.Middleware() | Generates or passes through a unique request ID; stores it in context |
middleware/recovery | recovery.Middleware(recovery.Config{Logger: logger}) | Catches panics, logs sanitized panic metadata, returns 500 |
middleware/accesslog | accesslog.Middleware(accesslog.Config{Logger: logger}) | Logs method, path, status, and duration for every request |
middleware/auth | authmw.Authenticate(authenticator) | Constructs auth middleware with startup validation; returns 401 on authentication failure |
middleware/security | secmw.Middleware(secmw.Config{Policy: policy}) | Sets Content-Security-Policy, X-Frame-Options, HSTS, and related headers |
middleware/cors | cors.Middleware(opts) | Handles preflight and cross-origin request headers |
middleware/timeout | timeout.Middleware(cfg) | Cancels context and returns 504 if handler exceeds the deadline |
middleware/ratelimit | ratelimit.AbuseGuard(cfg) | Token-bucket rate limiter; returns 429 with Retry-After |
middleware/bodylimit | bodylimit.BodyLimit(maxBytes, logger) | Rejects requests with body larger than limit with 413 |
middleware/compression | compression.Middleware(cfg) | Gzip-compresses responses for clients that accept it |
middleware/httpmetrics | httpmetrics.Middleware(observer) | Records HTTP request count, duration, and status distribution |
middleware/tracing | tracing.Middleware(tracer) | Injects or propagates distributed trace context |
middleware/concurrencylimit | concurrencylimit.Middleware(max, queue, timeout) | Limits concurrent in-flight requests; queues up to queue depth |
middleware/debug | debug.Middleware(cfg) | Captures response errors for dev-mode inspection |
middleware/coalesce | coalesce.New(cfg).Middleware() | Deduplicates identical in-flight requests (request coalescing) |
middleware/conformance | conformance.Middleware(...) | Validates request/response conformance against declared contracts |
Recommended default stack
Section titled “Recommended default stack”This is the canonical ordering from the reference service:
import ( "github.com/spcent/plumego/middleware/requestid" "github.com/spcent/plumego/middleware/recovery" "github.com/spcent/plumego/middleware/accesslog")
app.Use(requestid.Middleware()) // 1. assign ID firstrecoveryMw, err := recovery.Middleware(recovery.Config{Logger: app.Logger()})if err != nil { return err}app.Use(recoveryMw) // 2. recover panicsaccesslogMw, err := accesslog.Middleware(accesslog.Config{Logger: app.Logger()})if err != nil { return err}app.Use(accesslogMw) // 3. log requestsAdd auth, CORS, rate limiting, and timeout as needed for your service:
import ( authmw "github.com/spcent/plumego/middleware/auth" cors "github.com/spcent/plumego/middleware/cors" to "github.com/spcent/plumego/middleware/timeout")
app.Use(cors.Middleware(cors.CORSOptions{AllowedOrigins: []string{"https://myapp.com"}}))app.Use(rateLimitMiddleware)app.Use(to.Middleware(to.Config{Timeout: 10 * time.Second}))authMw, err := authmw.Authenticate(jwtManager.Authenticator(jwt.TokenTypeAccess))if err != nil { return err}app.Use(authMw)Custom middleware
Section titled “Custom middleware”Any func(http.Handler) http.Handler works:
func MyMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // pre-processing next.ServeHTTP(w, r) // post-processing })}
app.Use(middleware.Middleware(MyMiddleware))Why this primer exists
Section titled “Why this primer exists”middleware is easy to overgrow because many behaviors happen in the request path. The current repository keeps the line narrow on purpose: if the behavior is no longer transport-only, the stable middleware layer is already the wrong place.