Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 via ctx.Abort(...)) and return nil without calling next(). 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.

PositionWhen the middleware runs
maniflex.Before (default)before the default handler
maniflex.Afterafter the default handler
maniflex.Replaceinstead 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

StepWhat middleware here typically does
AuthVerify a token, populate ctx.Auth, reject unauthenticated requests.
DeserializeRarely customised. After middleware can rewrite the body via ctx.SetField / ctx.DeleteField.
ValidateCustom validation that goes beyond mfx: tags. Abort with 422 on failure.
ServiceBusiness logic — derive fields, call external services, start transactions (maniflex.WithTransaction).
DBHooks around the database call. After middleware sees ctx.DBResult; Replace substitutes a different backend.
ResponseAfter 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.
  • Transactionsmaniflex.WithTransaction as a Service-step middleware.
  • Error Handling — what Abort produces and how it propagates.