Skip to main content

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.Context to pass data between middleware and handlers. Avoid using global variables.
Always handle errors from the next handler. Consider whether to wrap or transform errors.
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

Build docs developers (and LLMs) love