Introduction
Middleware in Kratos provides a flexible way to intercept and process requests and responses in both HTTP and gRPC services. Middleware can be used for cross-cutting concerns like logging, metrics, tracing, authentication, and more.
Middleware Type
Kratos middleware is defined as a function that wraps a handler:
type Handler func ( ctx context . Context , req any ) ( any , error )
type Middleware func ( Handler ) Handler
This design allows middleware to:
Execute code before the handler
Pass control to the next handler
Execute code after the handler
Modify the request or response
Short-circuit the request chain
Basic Usage
Server Middleware
Apply middleware to HTTP or gRPC servers:
import (
" github.com/go-kratos/kratos/v2 "
" github.com/go-kratos/kratos/v2/middleware/recovery "
" github.com/go-kratos/kratos/v2/middleware/logging "
" github.com/go-kratos/kratos/v2/transport/http "
" github.com/go-kratos/kratos/v2/transport/grpc "
)
// HTTP Server
httpSrv := http . NewServer (
http . Address ( ":8000" ),
http . Middleware (
recovery . Recovery (),
logging . Server ( logger ),
),
)
// gRPC Server
grpcSrv := grpc . NewServer (
grpc . Address ( ":9000" ),
grpc . Middleware (
recovery . Recovery (),
logging . Server ( logger ),
),
)
Client Middleware
Apply middleware to HTTP or gRPC clients:
import (
" github.com/go-kratos/kratos/v2/middleware/logging "
" github.com/go-kratos/kratos/v2/middleware/tracing "
" github.com/go-kratos/kratos/v2/transport/http "
)
// HTTP Client
conn , err := http . NewClient (
context . Background (),
http . WithEndpoint ( "127.0.0.1:8000" ),
http . WithMiddleware (
tracing . Client (),
logging . Client ( logger ),
),
)
Chaining Middleware
The Chain function combines multiple middleware into a single middleware:
import " github.com/go-kratos/kratos/v2/middleware "
// Chain returns a Middleware that specifies the chained handler for endpoint
func Chain ( m ... Middleware ) Middleware {
return func ( next Handler ) Handler {
for i := len ( m ) - 1 ; i >= 0 ; i -- {
next = m [ i ]( next )
}
return next
}
}
Usage Example
import (
" github.com/go-kratos/kratos/v2/middleware "
" github.com/go-kratos/kratos/v2/middleware/recovery "
" github.com/go-kratos/kratos/v2/middleware/tracing "
" github.com/go-kratos/kratos/v2/middleware/logging "
)
// Create a chained middleware
chainedMiddleware := middleware . Chain (
recovery . Recovery (),
tracing . Server (),
logging . Server ( logger ),
)
// Apply to server
httpSrv := http . NewServer (
http . Address ( ":8000" ),
http . Middleware ( chainedMiddleware ),
)
Middleware is executed in the order specified. The first middleware in the chain executes first on the way in, and last on the way out.
Execution Order
When multiple middleware are chained, they execute in the following order:
Request → M1 (before) → M2 (before) → M3 (before) → Handler → M3 (after) → M2 (after) → M1 (after) → Response
Example:
http . Middleware (
recovery . Recovery (), // Executes first (outermost)
logging . Server ( logger ), // Executes second
metrics . Server (), // Executes third (innermost)
)
Creating Custom Middleware
You can create custom middleware by implementing the Middleware function signature:
import (
" context "
" fmt "
" time "
" github.com/go-kratos/kratos/v2/middleware "
)
// Custom middleware that adds a request ID
func RequestID () middleware . Middleware {
return func ( handler middleware . Handler ) middleware . Handler {
return func ( ctx context . Context , req any ) ( any , error ) {
// Before handler execution
requestID := generateRequestID ()
ctx = context . WithValue ( ctx , "request_id" , requestID )
fmt . Printf ( "[ %s ] Request started \n " , requestID )
start := time . Now ()
// Call the next handler
reply , err := handler ( ctx , req )
// After handler execution
duration := time . Since ( start )
fmt . Printf ( "[ %s ] Request completed in %v \n " , requestID , duration )
return reply , err
}
}
}
func generateRequestID () string {
return fmt . Sprintf ( " %d " , time . Now (). UnixNano ())
}
Middleware with Options
Create configurable middleware using the options pattern:
import (
" context "
" github.com/go-kratos/kratos/v2/middleware "
)
// Option is middleware option
type Option func ( * options )
type options struct {
prefix string
enabled bool
}
// WithPrefix sets the log prefix
func WithPrefix ( prefix string ) Option {
return func ( o * options ) {
o . prefix = prefix
}
}
// WithEnabled enables/disables the middleware
func WithEnabled ( enabled bool ) Option {
return func ( o * options ) {
o . enabled = enabled
}
}
// CustomMiddleware creates a custom middleware with options
func CustomMiddleware ( opts ... Option ) middleware . Middleware {
// Apply options
o := & options {
prefix : "[INFO]" ,
enabled : true ,
}
for _ , opt := range opts {
opt ( o )
}
return func ( handler middleware . Handler ) middleware . Handler {
return func ( ctx context . Context , req any ) ( any , error ) {
if ! o . enabled {
return handler ( ctx , req )
}
// Middleware logic here
fmt . Printf ( " %s Processing request \n " , o . prefix )
return handler ( ctx , req )
}
}
}
Conditional Middleware with Selector
Use the selector middleware to apply middleware conditionally based on operation paths:
import (
" github.com/go-kratos/kratos/v2/middleware/auth/jwt "
" github.com/go-kratos/kratos/v2/middleware/selector "
)
// Apply JWT middleware only to specific paths
http . Middleware (
selector . Server (
jwt . Server ( keyFunc ),
). Path ( "/api.v1.UserService/GetUser" , "/api.v1.UserService/UpdateUser" ). Build (),
)
// Apply middleware with prefix matching
http . Middleware (
selector . Server (
jwt . Server ( keyFunc ),
). Prefix ( "/api.v1.Admin" ). Build (),
)
// Apply middleware with regex matching
http . Middleware (
selector . Server (
jwt . Server ( keyFunc ),
). Regex ( "^/api.v1/user/.*" ). Build (),
)
// Apply middleware with custom match function
http . Middleware (
selector . Server (
jwt . Server ( keyFunc ),
). Match ( func ( ctx context . Context , operation string ) bool {
// Custom logic to determine if middleware should apply
return strings . HasPrefix ( operation , "/admin" )
}). Build (),
)
Built-in Middleware
Kratos provides several built-in middleware:
Best Practices
Place recovery middleware first to catch panics from all other middleware. Place authentication before authorization. Place metrics and tracing early to capture full request lifecycle.
Each middleware should handle a single concern. This makes them easier to test, reuse, and maintain.
Use Context for Passing Data
Use context.Context to pass data between middleware and handlers. Avoid using global variables.
Handle Errors Appropriately
Always handle errors from the next handler. Consider whether to wrap or transform errors.
Performance Considerations
Middleware executes on every request. Keep processing lightweight and avoid blocking operations.
Next Steps
Tracing Add OpenTelemetry tracing to your services
Metrics Collect Prometheus metrics
Authentication Secure your APIs with JWT
Rate Limiting Protect services with rate limiting