Docs Middleware

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.