How to Define and Apply Custom Middleware Chains in Kratos: A Complete Guide
Define custom middleware chains in Kratos by implementing the Middleware type as a higher-order function, compose multiple middlewares using middleware.Chain, and apply them globally via server options, selectively via the Use method with path selectors, or per-client via WithMiddleware.
The Kratos framework from the go-kratos/kratos repository treats every cross-cutting concern—such as logging, authentication, tracing, and validation—as a middleware. Understanding how to define and apply custom middleware chains in Kratos allows you to build flexible, reusable processing pipelines for both HTTP and gRPC transports.
Understanding the Kratos Middleware Interface
At the core of Kratos middleware architecture are two type definitions located in middleware/middleware.go. The Handler represents the endpoint execution, while Middleware is a higher-order function that wraps a Handler to intercept requests and responses.
// middleware/middleware.go
type Handler func(ctx context.Context, req any) (any, error)
type Middleware func(Handler) Handler
Every custom middleware you create must conform to the Middleware signature. It receives a Handler and returns a new Handler that executes your custom logic before or after calling the next handler in the chain.
Composing Middleware with Chain
The middleware.Chain function composes multiple Middleware values into a single executable chain. According to the implementation in middleware/middleware.go, the chain executes right-to-left, meaning the first middleware passed to Chain becomes the outermost wrapper and executes first on the way in and last on the way out.
// Example: Building a custom middleware chain
authMw := func(next middleware.Handler) middleware.Handler {
return func(ctx context.Context, req any) (any, error) {
// Authentication logic
if !validateToken(ctx) {
return nil, errors.Unauthenticated("invalid token")
}
return next(ctx, req)
}
}
logMw := func(next middleware.Handler) middleware.Handler {
return func(ctx context.Context, req any) (any, error) {
log.Infof("request: %v", req)
resp, err := next(ctx, req)
log.Infof("response: %v, err: %v", resp, err)
return resp, err
}
}
// Compose them: auth runs first, then logging, then the handler
myChain := middleware.Chain(authMw, logMw)
Applying Middleware Chains in Kratos
Kratos provides four distinct scopes for applying your custom middleware chains, each controlled through different configuration points in the transport layer.
Server-Wide Configuration
To apply a middleware chain to every request handled by an HTTP or gRPC server, pass the chain as a server option. In transport/http/server.go and transport/grpc/server.go, the Middleware option accepts a pre-composed chain.
// HTTP server with global middleware
srv := http.NewServer(
http.Address(":8080"),
http.Middleware(myChain), // Applies to all HTTP routes
)
// gRPC server with global middleware
grpcSrv := grpc.NewServer(
grpc.Middleware(myChain), // Applies to all gRPC methods
)
Service-Level and Method-Level Application
For selective application, the Server.Use method accepts a path selector and variadic middlewares. As implemented in both transport/http/server.go and transport/grpc/server.go, Kratos stores these in a matcher.Matcher instance and assembles the chain at request time based on the operation name.
grpcSrv := grpc.NewServer()
// Apply only to Greeter service methods
grpcSrv.Use("/helloworld.v1.Greeter/*", authMw, logMw)
The selector supports wildcard patterns, allowing you to target specific services or individual methods without affecting others.
Client-Side Middleware Application
When constructing HTTP or gRPC clients in transport/http/client.go, use the WithMiddleware option to attach middleware chains to all outgoing calls from that client instance.
cli, _ := http.NewClient(
context.Background(),
http.WithEndpoint("http://127.0.0.1:8080"),
http.WithMiddleware(authMw, logMw), // Applies to all outgoing calls
)
Internally, the client's invoke method checks client.opts.middleware and builds a temporary chain using middleware.Chain(client.opts.middleware...)(h) before executing the request.
Per-Call Middleware Wrapping
For one-off execution or dynamic composition, manually wrap a handler with your chain at the call site. This pattern bypasses the automatic middleware assembly and gives you direct control over the execution flow.
handler := func(ctx context.Context, req any) (any, error) {
// Actual business logic
return processRequest(ctx, req)
}
// Wrap and execute immediately
wrapped := myChain(handler)
resp, err := wrapped(ctx, req)
Practical Implementation Examples
The following examples demonstrate complete, reusable middleware implementations that work across both HTTP and gRPC transports.
Logging Middleware
// internal/mw/logging.go
func Logging() middleware.Middleware {
return func(next middleware.Handler) middleware.Handler {
return func(ctx context.Context, req any) (any, error) {
log.Infof("[REQ] %v", req)
resp, err := next(ctx, req)
log.Infof("[RESP] %v, err=%v", resp, err)
return resp, err
}
}
}
Authentication Middleware
// internal/mw/auth.go
func Auth() middleware.Middleware {
return func(next middleware.Handler) middleware.Handler {
return func(ctx context context.Context, req any) (any, error) {
token := extractToken(ctx)
if !validateToken(token) {
return nil, errors.Unauthenticated("invalid token")
}
return next(ctx, req)
}
}
}
Complete Server Setup
// Global application
srv := http.NewServer(
http.Address(":8080"),
http.Middleware(middleware.Chain(mw.Auth(), mw.Logging())),
)
// Service-specific application
grpcSrv := grpc.NewServer()
grpcSrv.Use("/helloworld.v1.Greeter/*", mw.Auth(), mw.Logging())
Summary
- Define custom middleware by implementing the
func(Handler) Handlersignature defined inmiddleware/middleware.go. - Compose multiple middlewares using
middleware.Chain, which executes them right-to-left (first middleware wraps outermost). - Apply globally using
http.Middleware()orgrpc.Middleware()server options to affect all traffic. - Apply selectively using
srv.Use(selector, m...)with path patterns to target specific services or methods, utilizing theinternal/matcher/matcher.goimplementation. - Apply on clients using
http.WithMiddleware()when constructing clients intransport/http/client.go. - Reuse the same middleware implementations across both HTTP and gRPC transports since both use the identical
Middlewaretype.
Frequently Asked Questions
What is the execution order of middleware.Chain in Kratos?
middleware.Chain executes middlewares right-to-left. When you call middleware.Chain(authMw, logMw, recoveryMw), the execution flow is: authMw executes first, calls logMw, which calls recoveryMw, which finally calls the handler. On the return path, the order reverses.
How do I apply different middleware to specific gRPC methods?
Use the Use method on your gRPC server instance with a path selector. For example, grpcSrv.Use("/helloworld.v1.Greeter/SayHello", authMw) applies authMw only to the SayHello method. Wildcards like /helloworld.v1.Greeter/* apply to all methods in the Greeter service.
Can I reuse the same middleware for both HTTP and gRPC in Kratos?
Yes. Both the HTTP transport (transport/http) and gRPC transport (transport/grpc) import and use the same Middleware type defined in middleware/middleware.go. A single middleware implementation works across both protocols without modification.
How do I pass metadata between middleware in Kratos?
Use the context.Context to carry values between middleware layers. Extract information in an upstream middleware using ctx = context.WithValue(ctx, key, value), then retrieve it in downstream middleware or the final handler using ctx.Value(key). Ensure you use struct keys rather than string keys to avoid collisions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →