Middleware
Middleware defines how requests move through the system.
It is not a feature layer.
It is not a plugin system.
It is not a dumping ground for logic.
In Plumego, middleware is treated as explicit control flow —
and therefore designed to be simple, visible, and predictable.
What Middleware Is
A Plumego middleware is a function that:
- Receives the request-scoped Context
- Executes logic before and/or after the next step
- Decides whether request processing continues
Conceptually:
type Middleware func(ctx *Context, next NextFunc)
Middleware wraps handlers and other middleware.
That is all it does.
What Middleware Is Not
Middleware is not:
- A service container
- A business logic layer
- An authorization engine for complex rules
- A background task runner
- A framework extension point with hidden hooks
If middleware starts to answer business questions,
it has already crossed its boundary.
Middleware Execution Model
The execution model is deterministic and linear.
For a single request:
Middleware A (before)
→ Middleware B (before)
→ Handler
← Middleware B (after)
← Middleware A (after)
Key guarantees:
- Middleware executes in registration order
- Each middleware decides whether to call
next() - If
next()is not called, execution stops - No middleware runs implicitly
There is no hidden branching.
Before and After Semantics
Middleware may:
- Execute logic before calling
next() - Execute logic after
next()returns - Or both
Typical patterns include:
- Timing measurements
- Logging
- Panic recovery
- Resource cleanup
Example (conceptual):
func ExampleMiddleware() Middleware {
return func(ctx *Context, next NextFunc) {
// before
start := time.Now()
next()
// after
logDuration(time.Since(start))
}
}
Middleware as Control Flow
Middleware does not just “add behavior”.
It controls whether and how a request proceeds.
Examples of legitimate control decisions:
- Reject unauthenticated requests
- Abort on invalid signatures
- Stop processing on rate limits
- Recover from panics
Because middleware controls flow,
its ordering and scope must be intentional.
Global vs Scoped Middleware
Plumego supports explicit scoping.
Global middleware
Applied to every request:
- Trace ID
- Logging
- Panic recovery
Registered once at startup.
Scoped middleware
Applied to a subset of routes:
- Authentication
- Authorization
- Feature-specific constraints
Applied via route groups.
Scope must be visible in routing code.
Middleware and Context Interaction
Middleware may:
- Read from Context
- Add request-scoped metadata to Context
Middleware must not:
- Store global state
- Replace the Context
- Inject services into Context
- Mutate domain data
Context is a carrier, not a dependency source.
Stopping the Chain Intentionally
A middleware may decide to stop request processing.
Example reasons:
- Authentication failure
- Invalid signature
- Rate limit exceeded
In these cases:
- Middleware writes a response
- Middleware does not call
next()
This behavior must be explicit and easy to trace.
Error Handling in Middleware
Middleware may handle cross-cutting errors, such as:
- Panics
- Timeouts
- Infrastructure failures at the boundary
Middleware should not translate domain errors.
Business errors belong to handlers.
Middleware and Determinism
A key Plumego guarantee:
Given the same middleware registration and the same request, execution order is always the same.
There is:
- No middleware auto-registration
- No hidden defaults
- No reflection-based ordering
- No environment-dependent chains
Determinism is a feature, not a limitation.
Writing Boring Middleware (On Purpose)
Good middleware is boring:
- Short
- Single-purpose
- Easy to remove
- Easy to reason about
Middleware that is “clever” often becomes:
- Hard to debug
- Hard to test
- Hard to evolve
Plumego deliberately rewards boring middleware.
Middleware and Testing
Middleware is easy to test because:
- Input is a Context
- Output is observable behavior
- Side effects are explicit
Tests should verify:
- Whether
next()is called - Whether Context is modified correctly
- Whether responses are written as expected
No framework magic is required.
Common Anti-Patterns
Middleware Doing Business Logic
This hides rules and breaks layering.
Implicit Middleware via init()
This destroys visibility and predictability.
Middleware That Depends on Other Middleware Implicitly
Ordering assumptions must be explicit in registration.
Using Middleware as a Service Locator
This couples everything to Context and kills testability.
Why Plumego Keeps Middleware Simple
Middleware is one of the easiest places for frameworks to become “smart”.
Smart middleware leads to:
- Hidden behavior
- Debugging surprises
- Accidental coupling
Plumego keeps middleware simple so that:
Control flow remains in your hands, not in the framework.
Summary
In Plumego:
- Middleware is explicit control flow
- Execution order is deterministic
- Scope is visible
- Responsibilities are narrow
- No hidden behavior exists
Middleware prepares the ground —
handlers and usecases decide what happens next.
Core Section Complete
With Context, Router, and Middleware understood,
you now fully understand everything Plumego’s core does.
Nothing more is hidden.
Everything else in Plumego — architecture, guides, patterns —
builds deliberately on this minimal, predictable foundation.