Middleware
5 minute read
Overview
Middleware functions execute before and after route handlers. They add cross-cutting concerns like logging, authentication, and rate limiting.
The app package provides access to high-quality middleware from the router/middleware subpackages.
Using Middleware
Global Middleware
Apply middleware to all routes:
a := app.MustNew()
a.Use(requestid.New())
a.Use(cors.New(cors.WithAllowAllOrigins(true)))
// All routes registered after Use() will have this middleware
a.GET("/users", handler)
a.POST("/orders", handler)
Middleware During Initialization
Add middleware when creating the app:
a, err := app.New(
app.WithServiceName("my-api"),
app.WithMiddleware(
requestid.New(),
cors.New(cors.WithAllowAllOrigins(true)),
),
)
Default Middleware
The app package automatically includes recovery middleware by default in both development and production modes.
To disable default middleware:
a, err := app.New(
app.WithoutDefaultMiddleware(),
app.WithMiddleware(myCustomRecovery), // Add your own
)
Built-in Middleware
Request ID
Generate unique request IDs for tracing:
import "rivaas.dev/router/middleware/requestid"
a.Use(requestid.New())
// Access in handler
a.GET("/", func(c *app.Context) {
reqID := c.Response.Header().Get("X-Request-ID")
c.JSON(http.StatusOK, map[string]string{
"request_id": reqID,
})
})
Options:
requestid.New(
requestid.WithRequestIDHeader("X-Correlation-ID"),
requestid.WithGenerator(customGenerator),
)
CORS
Handle Cross-Origin Resource Sharing:
import "rivaas.dev/router/middleware/cors"
// Allow all origins (development)
a.Use(cors.New(cors.WithAllowAllOrigins(true)))
// Specific origins (production)
a.Use(cors.New(
cors.WithAllowedOrigins([]string{"https://example.com"}),
cors.WithAllowCredentials(true),
cors.WithAllowedMethods([]string{"GET", "POST", "PUT", "DELETE"}),
cors.WithAllowedHeaders([]string{"Content-Type", "Authorization"}),
))
Recovery
Recover from panics gracefully (included by default):
import "rivaas.dev/router/middleware/recovery"
a.Use(recovery.New(
recovery.WithStackTrace(true),
))
Access Logging
Log HTTP requests (when not using app’s built-in observability):
import "rivaas.dev/router/middleware/accesslog"
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
a.Use(accesslog.New(
accesslog.WithLogger(logger),
accesslog.WithSkipPaths([]string{"/health", "/metrics"}),
))
Note: The app package automatically configures access logging through its unified observability when WithLogging() is used.
Timeout
Add request timeout handling:
import "rivaas.dev/router/middleware/timeout"
// Default timeout (30s)
a.Use(timeout.New())
// Custom timeout
a.Use(timeout.New(
timeout.WithDuration(5 * time.Second),
timeout.WithSkipPaths("/stream"),
timeout.WithSkipPrefix("/admin"),
))
Rate Limiting
Rate limit requests (single-instance only):
import "rivaas.dev/router/middleware/ratelimit"
// 100 requests per minute
a.Use(ratelimit.New(100, time.Minute))
Note: This is in-memory rate limiting suitable for single-instance deployments only. For production with multiple instances, use a distributed rate limiting solution.
Compression
Compress responses with gzip or brotli:
import "rivaas.dev/router/middleware/compression"
a.Use(compression.New(
compression.WithLevel(compression.BestSpeed),
compression.WithMinSize(1024), // Only compress responses > 1KB
))
Body Limit
Limit request body size:
import "rivaas.dev/router/middleware/bodylimit"
a.Use(bodylimit.New(
bodylimit.WithMaxBytes(5 << 20), // 5MB max
))
Security Headers
Add security headers (HSTS, CSP, etc.):
import "rivaas.dev/router/middleware/securityheaders"
a.Use(securityheaders.New(
securityheaders.WithHSTS(true),
securityheaders.WithContentSecurityPolicy("default-src 'self'"),
securityheaders.WithXFrameOptions("DENY"),
))
Basic Auth
HTTP Basic Authentication:
import "rivaas.dev/router/middleware/basicauth"
a.Use(basicauth.New(
basicauth.WithUsers(map[string]string{
"admin": "password123",
}),
basicauth.WithRealm("Admin Area"),
))
Custom Middleware
Writing Custom Middleware
Create custom middleware as functions:
func AuthMiddleware() app.HandlerFunc {
return func(c *app.Context) {
token := c.Request.Header.Get("Authorization")
if token == "" {
c.Unauthorized("missing authorization token")
return
}
// Validate token...
if !isValid(token) {
c.Unauthorized("invalid token")
return
}
// Continue to next middleware/handler
c.Next()
}
}
// Use it
a.Use(AuthMiddleware())
Middleware with Configuration
Create configurable middleware:
type AuthConfig struct {
TokenHeader string
SkipPaths []string
}
func AuthWithConfig(config AuthConfig) app.HandlerFunc {
return func(c *app.Context) {
// Skip authentication for certain paths
for _, path := range config.SkipPaths {
if c.Request.URL.Path == path {
c.Next()
return
}
}
token := c.Request.Header.Get(config.TokenHeader)
if token == "" || !isValid(token) {
c.Unauthorized("authentication failed")
return
}
c.Next()
}
}
// Use it
a.Use(AuthWithConfig(AuthConfig{
TokenHeader: "X-API-Key",
SkipPaths: []string{"/health", "/public"},
}))
Middleware with State
Share state across requests:
type RateLimiter struct {
requests map[string]int
mu sync.Mutex
}
func NewRateLimiter() *RateLimiter {
return &RateLimiter{
requests: make(map[string]int),
}
}
func (rl *RateLimiter) Middleware() app.HandlerFunc {
return func(c *app.Context) {
clientIP := c.ClientIP()
rl.mu.Lock()
count := rl.requests[clientIP]
rl.requests[clientIP]++
rl.mu.Unlock()
if count > 100 {
c.Status(http.StatusTooManyRequests)
return
}
c.Next()
}
}
// Use it
limiter := NewRateLimiter()
a.Use(limiter.Middleware())
Route-Specific Middleware
Per-Route Middleware
Apply middleware to specific routes:
// Using WithBefore option
a.GET("/admin", adminHandler,
app.WithBefore(AuthMiddleware()),
)
// Multiple middleware
a.GET("/admin/users", handler,
app.WithBefore(
AuthMiddleware(),
AdminOnlyMiddleware(),
),
)
After Middleware
Execute middleware after the handler:
a.GET("/orders/:id", handler,
app.WithAfter(AuditLogMiddleware()),
)
Combined Middleware
Combine before and after middleware:
a.POST("/orders", handler,
app.WithBefore(AuthMiddleware(), RateLimitMiddleware()),
app.WithAfter(AuditLogMiddleware()),
)
Group Middleware
Apply middleware to route groups:
// Admin routes with auth middleware
admin := a.Group("/admin", AuthMiddleware(), AdminOnlyMiddleware())
admin.GET("/users", getUsersHandler)
admin.POST("/users", createUserHandler)
// API routes with rate limiting
api := a.Group("/api", RateLimitMiddleware())
api.GET("/status", statusHandler)
api.GET("/version", versionHandler)
Middleware Execution Order
Middleware executes in the order it’s registered:
a.Use(Middleware1()) // Executes first
a.Use(Middleware2()) // Executes second
a.Use(Middleware3()) // Executes third
a.GET("/", handler) // Handler executes last
// Execution order:
// 1. Middleware1
// 2. Middleware2
// 3. Middleware3
// 4. handler
// 5. Middleware3 (after c.Next())
// 6. Middleware2 (after c.Next())
// 7. Middleware1 (after c.Next())
Complete Example
package main
import (
"log"
"net/http"
"time"
"rivaas.dev/app"
"rivaas.dev/router/middleware/requestid"
"rivaas.dev/router/middleware/cors"
"rivaas.dev/router/middleware/timeout"
)
func main() {
a, err := app.New(
app.WithServiceName("api"),
app.WithMiddleware(
requestid.New(),
cors.New(cors.WithAllowAllOrigins(true)),
timeout.New(timeout.WithDuration(30 * time.Second)),
),
)
if err != nil {
log.Fatal(err)
}
// Custom middleware
a.Use(LoggingMiddleware())
a.Use(AuthMiddleware())
// Public routes (no auth)
a.GET("/health", healthHandler)
// Protected routes (with auth)
a.GET("/users", usersHandler)
// Admin routes (with auth + admin check)
admin := a.Group("/admin", AdminOnlyMiddleware())
admin.GET("/dashboard", dashboardHandler)
// Start server...
}
func LoggingMiddleware() app.HandlerFunc {
return func(c *app.Context) {
start := time.Now()
c.Next()
duration := time.Since(start)
c.Logger().Info("request completed",
"method", c.Request.Method,
"path", c.Request.URL.Path,
"duration", duration,
)
}
}
func AuthMiddleware() app.HandlerFunc {
return func(c *app.Context) {
// Skip auth for health check
if c.Request.URL.Path == "/health" {
c.Next()
return
}
token := c.Request.Header.Get("Authorization")
if token == "" {
c.Unauthorized("missing authorization token")
return
}
c.Next()
}
}
func AdminOnlyMiddleware() app.HandlerFunc {
return func(c *app.Context) {
// Check if user is admin...
if !isAdmin() {
c.Forbidden("admin access required")
return
}
c.Next()
}
}
Next Steps
- Routing - Organize routes with groups and versioning
- Context - Access request and response in middleware
- Examples - See complete working examples
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.