Available Middleware
Steel provides a comprehensive set of built-in middleware for common web application needs. Each middleware is available in both standard and opinionated versions, with the opinionated versions contributing to OpenAPI documentation.
Core Middleware
Logger
Logs HTTP requests with method, path, and duration.
import "github.com/xraph/steel"
// Standard version
router.Use(steel.Logger)
// Usage example
router := steel.NewRouter()
router.Use(steel.Logger)
router.GET("/users", getUsersHandler)
// Output: GET /users 1.234msRecoverer
Recovers from panics and returns a 500 Internal Server Error response.
// Standard version
router.Use(steel.Recoverer)
// Usage example
router := steel.NewRouter()
router.Use(steel.Recoverer)
router.GET("/panic", func(w http.ResponseWriter, r *http.Request) {
panic("Something went wrong!")
// Returns 500 Internal Server Error instead of crashing
})Timeout
Sets a timeout for request processing.
// Standard version with 30-second timeout
router.Use(steel.Timeout(30 * time.Second))
// Usage example
router := steel.NewRouter()
router.Use(steel.Timeout(5 * time.Second))
router.GET("/slow", func(w http.ResponseWriter, r *http.Request) {
select {
case <-time.After(10 * time.Second):
w.Write([]byte("This will timeout"))
case <-r.Context().Done():
// Request timed out
return
}
})Security Middleware
CORS (Cross-Origin Resource Sharing)
Handles cross-origin requests with configurable policies.
import "github.com/xraph/steel/middleware"
// Default CORS configuration
router.Use(middleware.CORS())
// Custom CORS configuration
corsConfig := middleware.CORSConfig{
AllowedOrigins: []string{"https://myapp.com", "https://api.myapp.com"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Content-Type", "Authorization", "X-API-Key"},
ExposedHeaders: []string{"X-Total-Count"},
AllowCredentials: true,
MaxAge: 86400, // 24 hours
OptionsPassthrough: false,
}
router.Use(middleware.CORS(corsConfig))
// Opinionated version (contributes to OpenAPI)
router.UseOpinionated(middleware.OpinionatedCORS(corsConfig))Configuration Options:
AllowedOrigins: List of allowed origins or["*"]for any originAllowedMethods: HTTP methods to allowAllowedHeaders: Request headers to allowExposedHeaders: Response headers to expose to the clientAllowCredentials: Whether to allow credentials (cookies, authorization headers)MaxAge: How long browsers can cache preflight resultsOptionsPassthrough: Whether to pass OPTIONS requests to the next handler
Security Headers
Adds common security headers to responses.
// Default security headers
router.Use(middleware.SecureHeaders())
// Custom security configuration
securityConfig := middleware.SecurityConfig{
XSSProtection: "1; mode=block",
ContentTypeNosniff: true,
XFrameOptions: "DENY",
HSTSMaxAge: 31536000, // 1 year
HSTSIncludeSubdomains: true,
HSTSPreload: true,
ContentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline'",
ReferrerPolicy: "strict-origin-when-cross-origin",
PermissionsPolicy: "camera=(), microphone=(), geolocation=()",
}
router.Use(middleware.SecureHeaders(securityConfig))Headers Added:
X-XSS-Protection: XSS protection for older browsersX-Content-Type-Options: Prevents MIME type sniffingX-Frame-Options: Prevents clickjacking attacksStrict-Transport-Security: Enforces HTTPS connectionsContent-Security-Policy: Controls resource loadingReferrer-Policy: Controls referrer informationPermissions-Policy: Controls browser feature access
JWT Authentication
Validates JWT tokens and extracts user information.
// JWT configuration
jwtConfig := middleware.JWTConfig{
SigningKey: []byte("your-secret-key"),
SigningMethod: jwt.SigningMethodHS256,
TokenLookup: "header:Authorization", // or "query:token" or "cookie:jwt"
AuthScheme: "Bearer",
Claims: jwt.MapClaims{},
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, "Unauthorized: "+err.Error(), http.StatusUnauthorized)
},
}
// Standard version
router.Use(middleware.JWT(jwtConfig))
// Opinionated version (contributes to OpenAPI security)
router.UseOpinionated(middleware.OpinionatedJWT(jwtConfig))
// Register JWT security scheme for OpenAPI
router.RegisterSecurityScheme("JWTAuth", steel.BearerAuth(
"JWT Bearer token authentication",
"JWT",
))Token Lookup Options:
header:Authorization: Look for token in Authorization headerquery:token: Look for token in query parametercookie:jwt: Look for token in cookie
Rate Limiting
Prevents abuse by limiting request rates per client.
// Rate limiting configuration
rateLimitConfig := middleware.RateLimitConfig{
RequestsPerSecond: 10, // 10 requests per second
BurstSize: 20, // Allow bursts up to 20 requests
KeyFunc: func(r *http.Request) string {
// Custom key function (defaults to IP address)
return r.Header.Get("X-API-Key")
},
OnLimitReached: func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
},
SkipFunc: func(r *http.Request) bool {
// Skip rate limiting for admin users
return r.Header.Get("X-Admin") == "true"
},
}
// Standard version
router.Use(middleware.RateLimit(rateLimitConfig))
// Opinionated version (contributes to OpenAPI)
router.UseOpinionated(middleware.OpinionatedRateLimit(rateLimitConfig))Features:
- Per-IP rate limiting by default
- Custom key functions for different limiting strategies
- Configurable burst sizes
- Skip functions for exempting certain requests
- In-memory storage with automatic cleanup
- Pluggable storage backends
Request Context Middleware
Request ID
Generates and tracks unique request identifiers.
// Default configuration
router.Use(middleware.RequestID())
// Custom configuration
requestIDConfig := middleware.RequestIDConfig{
HeaderName: "X-Request-ID",
Generator: func() string { return uuid.New().String() },
ForceGenerate: false, // Don't override existing request IDs
}
router.Use(middleware.RequestID(requestIDConfig))
// Opinionated version
router.UseOpinionated(middleware.OpinionatedRequestID(requestIDConfig))
// Access request ID in handlers
func myHandler(w http.ResponseWriter, r *http.Request) {
requestID := r.Context().Value("request_id").(string)
fmt.Printf("Processing request: %s", requestID)
}Performance Middleware
Compression
Compresses response bodies to reduce bandwidth usage.
// Default compression
router.Use(middleware.Compression())
// Custom compression configuration
compressionConfig := middleware.CompressionConfig{
Level: gzip.BestCompression,
MinLength: 1024, // Only compress responses >= 1KB
Types: []string{
"text/html",
"text/css",
"text/javascript",
"application/json",
"application/xml",
},
}
router.Use(middleware.Compression(compressionConfig))Features:
- Gzip compression for supported content types
- Configurable compression levels
- Minimum size thresholds
- Content-type filtering
- Automatic
Content-Encodingheaders
Body Size Limit
Limits the size of request bodies to prevent memory exhaustion.
// 10MB limit
router.Use(middleware.BodyLimit(10 << 20))
// With custom configuration
bodyLimitConfig := middleware.BodyLimitConfig{
Limit: 5 << 20, // 5MB
SkipFunc: func(r *http.Request) bool {
// Skip limit for file upload endpoints
return strings.HasPrefix(r.URL.Path, "/upload")
},
}
router.Use(middleware.BodyLimit(5<<20, bodyLimitConfig))Advanced Middleware
Circuit Breaker
Prevents cascading failures by temporarily disabling failing services.
// Circuit breaker configuration
circuitConfig := middleware.CircuitBreakerConfig{
MaxRequests: 3, // Max requests in half-open state
Interval: 60 * time.Second, // Reset interval
Timeout: 30 * time.Second, // Open state timeout
ReadyToTrip: func(counts middleware.Counts) bool {
return counts.ConsecutiveFailures > 5
},
OnStateChange: func(name string, from, to middleware.State) {
log.Printf("Circuit breaker %s: %v -> %v", name, from, to)
},
IsSuccessful: func(err error) bool {
return err == nil
},
}
router.Use(middleware.CircuitBreakerMiddleware(circuitConfig))States:
- Closed: Normal operation, requests flow through
- Open: Circuit is tripped, requests fail fast
- Half-Open: Testing if service has recovered
Metrics Collection
Collects request metrics for monitoring and analytics.
// Create metrics instance
metrics := middleware.NewMetrics()
// Metrics configuration
metricsConfig := middleware.MetricsConfig{
Namespace: "myapp",
Subsystem: "api",
SkipFunc: func(r *http.Request) bool {
return r.URL.Path == "/health"
},
GroupedPath: func(path string) string {
// Group similar paths for better metrics
if strings.HasPrefix(path, "/users/") {
return "/users/:id"
}
return path
},
}
router.Use(middleware.MetricsMiddleware(metrics, metricsConfig))
// Access metrics
stats := metrics.GetStats()
fmt.Printf("Metrics: %+v", stats)Request Logging
Advanced request logging with structured output.
// Custom logging configuration
loggingConfig := middleware.LoggingConfig{
Logger: log.New(os.Stdout, "", log.LstdFlags),
Format: "${time} ${method} ${path} ${status} ${size} ${duration}",
TimeFormat: time.RFC3339,
UTC: true,
Skip: func(r *http.Request) bool {
return r.URL.Path == "/health"
},
CustomFields: map[string]func(*http.Request, time.Duration) interface{}{
"user_id": func(r *http.Request, d time.Duration) interface{} {
if userID := r.Header.Get("X-User-ID"); userID != "" {
return userID
}
return nil
},
},
}
router.Use(middleware.RequestLogging(loggingConfig))Middleware Combinations
Development Stack
func setupDevelopmentMiddleware(router *steel.SteelRouter) {
// Development-friendly middleware stack
router.Use(middleware.RequestID())
router.Use(steel.Logger)
router.Use(steel.Recoverer)
// Permissive CORS for development
router.Use(middleware.CORS(middleware.CORSConfig{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
AllowedHeaders: []string{"*"},
AllowCredentials: true,
}))
// Basic security headers
router.Use(middleware.SecureHeaders())
}Production Stack
func setupProductionMiddleware(router *steel.SteelRouter, config Config) {
// Production middleware stack
router.Use(middleware.RequestID())
router.Use(steel.Logger)
router.Use(steel.Recoverer)
// Security middleware
router.Use(middleware.SecureHeaders(middleware.SecurityConfig{
HSTSMaxAge: 31536000,
HSTSIncludeSubdomains: true,
HSTSPreload: true,
ContentSecurityPolicy: "default-src 'self'",
}))
// Performance middleware
router.Use(middleware.Compression())
router.Use(middleware.BodyLimit(10 << 20)) // 10MB
// Rate limiting
router.Use(middleware.RateLimit(middleware.RateLimitConfig{
RequestsPerSecond: 100,
BurstSize: 200,
}))
// CORS with specific origins
router.Use(middleware.CORS(middleware.CORSConfig{
AllowedOrigins: config.AllowedOrigins,
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
}))
// Circuit breaker for resilience
router.Use(middleware.CircuitBreakerMiddleware(middleware.CircuitBreakerConfig{
MaxRequests: 3,
Interval: time.Minute,
Timeout: 30 * time.Second,
}))
}API Gateway Stack
func setupGatewayMiddleware(router *steel.SteelRouter) {
// Metrics collection
metrics := middleware.NewMetrics()
router.Use(middleware.MetricsMiddleware(metrics))
// Request tracing
router.Use(middleware.RequestID())
router.Use(middleware.RequestLogging(middleware.LoggingConfig{
Format: "${time} ${method} ${path} ${status} ${duration} ${request_id}",
CustomFields: map[string]func(*http.Request, time.Duration) interface{}{
"service": func(r *http.Request, d time.Duration) interface{} {
return r.Header.Get("X-Service-Name")
},
},
}))
// Security
router.Use(middleware.SecureHeaders())
router.Use(middleware.RateLimit(middleware.RateLimitConfig{
RequestsPerSecond: 1000,
BurstSize: 2000,
}))
// Authentication for protected routes
router.Use(middleware.JWT(middleware.JWTConfig{
SigningKey: []byte(os.Getenv("JWT_SECRET")),
SigningMethod: jwt.SigningMethodHS256,
SkipFunc: func(r *http.Request) bool {
// Skip auth for public endpoints
publicPaths := []string{"/health", "/metrics", "/auth/login"}
for _, path := range publicPaths {
if r.URL.Path == path {
return true
}
}
return false
},
}))
// Performance
router.Use(middleware.Compression())
router.Use(steel.Timeout(30 * time.Second))
// Resilience
router.Use(middleware.CircuitBreakerMiddleware(middleware.CircuitBreakerConfig{
MaxRequests: 5,
Interval: time.Minute,
Timeout: 30 * time.Second,
}))
// Recovery
router.Use(steel.Recoverer)
}Middleware Helper Functions
Context Helpers
Steel provides helper functions to extract common values from request context:
import "github.com/xraph/steel/middleware"
func myHandler(w http.ResponseWriter, r *http.Request) {
// Get request ID
requestID := middleware.GetRequestID(r)
// Get JWT token
token := middleware.GetJWTToken(r)
// Get JWT claims
claims := middleware.GetJWTClaims(r)
// Get user ID from JWT claims
userID := middleware.GetUserID(r)
fmt.Printf("Request %s from user %s", requestID, userID)
}Custom Storage Backends
Implement custom storage for rate limiting:
type RedisRateLimitStore struct {
client *redis.Client
rps rate.Limit
burst int
}
func (s *RedisRateLimitStore) GetLimiter(key string) *rate.Limiter {
// Implement Redis-backed rate limiting
// This is a simplified example
return rate.NewLimiter(s.rps, s.burst)
}
func (s *RedisRateLimitStore) CleanupExpired() {
// Redis handles expiration automatically
}
// Use custom storage
redisStore := &RedisRateLimitStore{
client: redisClient,
rps: rate.Limit(100),
burst: 200,
}
rateLimitConfig := middleware.RateLimitConfig{
Store: redisStore,
}Testing Middleware
Test middleware in isolation:
func TestRateLimitMiddleware(t *testing.T) {
config := middleware.RateLimitConfig{
RequestsPerSecond: 1,
BurstSize: 1,
}
middleware := middleware.RateLimit(config)
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
// First request should succeed
req1 := httptest.NewRequest("GET", "/test", nil)
w1 := httptest.NewRecorder()
handler.ServeHTTP(w1, req1)
if w1.Code != http.StatusOK {
t.Errorf("Expected first request to succeed, got %d", w1.Code)
}
// Second request should be rate limited
req2 := httptest.NewRequest("GET", "/test", nil)
w2 := httptest.NewRecorder()
handler.ServeHTTP(w2, req2)
if w2.Code != http.StatusTooManyRequests {
t.Errorf("Expected second request to be rate limited, got %d", w2.Code)
}
}Performance Tips
Performance Tips:
- Order middleware by likelihood of early termination
- Use skip functions to avoid unnecessary processing
- Consider using opinionated middleware only where OpenAPI documentation is needed
- Enable compression for text-based responses
- Use appropriate rate limiting strategies for your use case
Optimal Middleware Ordering
// âś… Optimal order: fast-failing middleware first
router.Use(middleware.RateLimit(rateLimitConfig)) // Fast check, may reject early
router.Use(middleware.CORS()) // Quick header check
router.Use(middleware.JWT(jwtConfig)) // Authentication
router.Use(middleware.RequestID()) // Add request ID
router.Use(middleware.Compression()) // Response modification
router.Use(middleware.RequestLogging(logConfig)) // Always logs
router.Use(steel.Recoverer) // Always wraps handlerSteel’s middleware ecosystem provides everything you need to build secure, performant, and well-documented APIs while maintaining flexibility for custom requirements.