Writing Middleware
A middleware is a function that runs as part of one of the six pipeline steps. It can inspect and modify the request context, call into the database, decide whether to proceed, and inject behaviour before or after the step’s default handler.
Signature
type MiddlewareFunc func(ctx *maniflex.ServerContext, next func() error) error
A middleware does one of two things:
- Continue the pipeline — perform its work, then call
next()and return the result. The chain executes the remaining middleware in the step and then the steps after it. - Short-circuit — set
ctx.Response(typically viactx.Abort(...)) and returnnilwithout callingnext(). The remaining steps are skipped and the prepared response is written to the wire.
func bearerToken(ctx *maniflex.ServerContext, next func() error) error {
header := ctx.Request.Header.Get("Authorization")
if !strings.HasPrefix(header, "Bearer ") {
ctx.Abort(http.StatusUnauthorized, "UNAUTHORIZED", "missing bearer token")
return nil
}
ctx.Auth = &maniflex.AuthInfo{UserID: parseSubject(header)}
return next()
}
Typed middleware
For middleware that works with the request body, maniflex.Handle[T] adapts a
typed handler into a MiddlewareFunc. It hands you the bound record as a
concrete *T (the same value ctx.Record holds) instead of a map, runs only
when a *T body is bound, and is skipped on body-less operations:
server.Pipeline.Service.Register(
maniflex.Handle(func(ctx *maniflex.ServerContext, u *User) error {
if u.Age < 18 {
ctx.Abort(http.StatusUnprocessableEntity, "TOO_YOUNG", "must be 18+")
}
return nil
}),
maniflex.ForModel("User"), maniflex.ForOperation(maniflex.OpCreate),
)
To change a field, call ctx.SetField(name, value) rather than mutating the
struct, so the write reaches both the body and the record (see
ServerContext › The request body). For an ad-hoc typed read inside
a plain middleware, use maniflex.For[T](ctx) / maniflex.Bind[T](ctx).
Registration
Each pipeline step exposes a *StepRegistry on server.Pipeline. Register a
middleware on the step where its work belongs:
server.Pipeline.Auth.Register(bearerToken)
Without options, the middleware applies to every model and every operation.
Scoping
Two functional options narrow the scope. They are independent and may be combined.
ForModel(names ...string)
Restrict to one or more models, by struct name:
server.Pipeline.Service.Register(hashPassword, maniflex.ForModel("User"))
ForOperation(ops ...Operation)
Restrict to specific operations:
server.Pipeline.Auth.Register(requireToken,
maniflex.ForOperation(maniflex.OpCreate, maniflex.OpUpdate, maniflex.OpDelete),
)
Operation values: OpList, OpRead, OpCreate, OpUpdate, OpDelete,
OpHead, OpOptions, OpAction. Registering on Validate/Service/DB with
OpAction has no effect — those steps are skipped for action endpoints.
Combining
server.Pipeline.Service.Register(chargePayment,
maniflex.ForModel("Order"),
maniflex.ForOperation(maniflex.OpCreate),
)
Position
By default, a middleware runs before the step’s default handler. Use
AtPosition to change that.
| Position | When the middleware runs |
|---|---|
maniflex.Before (default) | before the default handler |
maniflex.After | after the default handler |
maniflex.Replace | instead of the default handler — the step’s built-in behaviour is skipped |
// Run after the DB step succeeds — useful for audit logs.
server.Pipeline.DB.Register(auditLog,
maniflex.ForOperation(maniflex.OpCreate, maniflex.OpUpdate, maniflex.OpDelete),
maniflex.AtPosition(maniflex.After),
)
// Replace the default DB step for one model entirely.
server.Pipeline.DB.Register(customDispatch,
maniflex.ForModel("LegacyOrder"),
maniflex.AtPosition(maniflex.Replace),
)
Within a step, all matching Before middlewares run in registration order,
then the core handler (default or Replace), then all matching After
middlewares in registration order. If multiple Replace middlewares match,
the last one registered wins.
Naming for traces
maniflex.WithName("name") attaches a human label to a middleware for use in
pipeline trace logs (enabled via Config.Trace). It does not change runtime
behaviour:
server.Pipeline.Auth.Register(rateLimit, maniflex.WithName("rate-limiter"))
Step-specific guidance
| Step | What middleware here typically does |
|---|---|
| Auth | Verify a token, populate ctx.Auth, reject unauthenticated requests. |
| Deserialize | Rarely customised. After middleware can rewrite the body via ctx.SetField / ctx.DeleteField. |
| Validate | Custom validation that goes beyond mfx: tags. Abort with 422 on failure. |
| Service | Business logic — derive fields, call external services, start transactions (maniflex.WithTransaction). |
| DB | Hooks around the database call. After middleware sees ctx.DBResult; Replace substitutes a different backend. |
| Response | After middleware can add headers; Replace lets you write a non-envelope response. |
After-middleware error handling
An After middleware sees ctx.Response when the default step has populated
it. Inspect it to decide whether to act:
func auditLog(ctx *maniflex.ServerContext, next func() error) error {
if err := next(); err != nil {
return err
}
// don't audit failed writes
if ctx.Response != nil && ctx.Response.StatusCode < 400 {
record(ctx)
}
return nil
}
Per-model middleware at registration
For middleware that belongs to exactly one model, ModelConfig.Middleware
attaches hooks scoped to that model at registration time, avoiding the separate
Register call:
server.MustRegister(
Order{}, maniflex.ModelConfig{
Middleware: &maniflex.ModelMiddleware{
Validate: []maniflex.MiddlewareFunc{checkStock},
Service: []maniflex.MiddlewareFunc{chargePayment},
},
},
)
Both forms are equivalent; choose whichever keeps the declaration close to the code that depends on it.
Built-in middleware
Several middleware functions ship with the framework or its satellite modules — JWT auth, password hashing, audit logging, CORS, and more. They are documented in Middleware Catalogue.
Next
- ServerContext — the fields a middleware reads and writes.
- Transactions —
maniflex.WithTransactionas a Service-step middleware. - Error Handling — what
Abortproduces and how it propagates.