Documentation Index Fetch the complete documentation index at: https://mintlify.com/platforma-dev/platforma/llms.txt
Use this file to discover all available pages before exploring further.
Middleware in Platforma wraps HTTP handlers to add cross-cutting concerns like authentication, logging, tracing, and request validation.
Middleware Interface
From httpserver/middleware.go:9, middleware implements a simple interface:
type Middleware interface {
// Wrap wraps an http.Handler with middleware logic
Wrap ( http . Handler ) http . Handler
}
Any type with a Wrap method can be used as middleware.
MiddlewareFunc
For simple middleware, use the MiddlewareFunc convenience type from httpserver/middleware.go:15:
type MiddlewareFunc func ( http . Handler ) http . Handler
func ( f MiddlewareFunc ) Wrap ( h http . Handler ) http . Handler {
return f ( h )
}
This lets you pass functions directly as middleware:
middleware := httpserver . MiddlewareFunc ( func ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
// Middleware logic
h . ServeHTTP ( w , r )
})
})
Creating Middleware
Basic Pattern
Middleware follows this structure:
func MyMiddleware ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
// Before the handler runs
h . ServeHTTP ( w , r ) // Call the next handler
// After the handler runs
})
}
Example: Request Logging
From demo-app/cmd/api/main.go:45, inline middleware for logging:
subApiGroup . UseFunc ( func ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
log . InfoContext ( r . Context (), "incoming request" , "addr" , r . RemoteAddr )
h . ServeHTTP ( w , r )
})
})
Example: TraceID Middleware
Real implementation from log/traceid.go:11:
package log
import (
" context "
" net/http "
" github.com/google/uuid "
)
type TraceIDMiddleware struct {
contextKey any
header string
}
func NewTraceIDMiddleware ( contextKey any , header string ) * TraceIDMiddleware {
if contextKey == nil {
contextKey = TraceIDKey
}
if header == "" {
header = "Platforma-Trace-Id"
}
return & TraceIDMiddleware {
contextKey : contextKey ,
header : header ,
}
}
func ( m * TraceIDMiddleware ) Wrap ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
// Generate trace ID
traceID := uuid . NewString ()
// Add to context
ctx := context . WithValue ( r . Context (), m . contextKey , traceID )
r = r . WithContext ( ctx )
// Add to response header
w . Header (). Set ( m . header , traceID )
// Call next handler
h . ServeHTTP ( w , r )
})
}
Usage from demo-app/cmd/api/main.go:34:
api . Use ( log . NewTraceIDMiddleware ( nil , "" ))
This middleware generates a unique trace ID for each request, adds it to the response headers, and makes it available in the request context for logging.
Applying Middleware
Use() - Apply Middleware
From httpserver/handlergroup.go:19, apply middleware to a server or group:
// Apply to entire server
api := httpserver . New ( "8080" , 5 * time . Second )
api . Use ( loggingMiddleware )
api . Use ( authMiddleware )
// Apply to handler group
adminAPI := httpserver . NewHandlerGroup ()
adminAPI . Use ( adminAuthMiddleware )
adminAPI . Use ( auditMiddleware )
UseFunc() - Apply Function Middleware
From httpserver/handlergroup.go:24, for inline middleware:
api . UseFunc ( func ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
// Middleware logic
h . ServeHTTP ( w , r )
})
})
Wrap() - Apply to Single Handler
From auth/domain.go:27, wrap individual handlers:
// Protect specific handlers
changePasswordHandler := authMiddleware . Wrap ( NewChangePasswordHandler ( service ))
deleteHandler := authMiddleware . Wrap ( NewDeleteHandler ( service ))
// Unprotected handler
loginHandler := NewLoginHandler ( service )
Middleware Execution Order
Middleware is applied in reverse order from httpserver/middleware.go:26:
func wrapHandlerInMiddleware ( handler http . Handler , middlewares [] Middleware ) http . Handler {
finalHandler := handler
for _ , middleware := range slices . Backward ( middlewares ) {
finalHandler = middleware . Wrap ( finalHandler )
}
return finalHandler
}
The first registered middleware is the outermost wrapper.
Example:
api . Use ( traceIDMiddleware ) // 1st registered - outermost
api . Use ( loggingMiddleware ) // 2nd
api . Use ( authMiddleware ) // 3rd registered - innermost
api . Handle ( "GET /users" , handler )
Execution flow:
Request enters traceIDMiddleware
Passes to loggingMiddleware
Passes to authMiddleware
Reaches handler
Response flows back through auth → logging → traceID
Common Middleware Patterns
Authentication
Check for valid credentials and inject user into context:
type AuthMiddleware struct {
authService AuthService
}
func ( m * AuthMiddleware ) Wrap ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
// Get session from cookie
cookie , err := r . Cookie ( "session" )
if err != nil {
http . Error ( w , "unauthorized" , http . StatusUnauthorized )
return
}
// Validate session
user , err := m . authService . GetUserBySession ( r . Context (), cookie . Value )
if err != nil {
http . Error ( w , "unauthorized" , http . StatusUnauthorized )
return
}
// Add user to context
ctx := context . WithValue ( r . Context (), userContextKey , user )
r = r . WithContext ( ctx )
h . ServeHTTP ( w , r )
})
}
Request Timing
Measure request duration:
func TimingMiddleware ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
start := time . Now ()
h . ServeHTTP ( w , r )
duration := time . Since ( start )
log . InfoContext ( r . Context (), "request completed" ,
"duration" , duration ,
"path" , r . URL . Path ,
)
})
}
Error Recovery
Recover from panics:
func RecoveryMiddleware ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
defer func () {
if err := recover (); err != nil {
log . ErrorContext ( r . Context (), "handler panicked" ,
"error" , err ,
"path" , r . URL . Path ,
)
http . Error ( w , "internal server error" ,
http . StatusInternalServerError )
}
}()
h . ServeHTTP ( w , r )
})
}
Add cross-origin resource sharing headers:
func CORSMiddleware ( allowedOrigin string ) httpserver . Middleware {
return httpserver . MiddlewareFunc ( func ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Header (). Set ( "Access-Control-Allow-Origin" , allowedOrigin )
w . Header (). Set ( "Access-Control-Allow-Methods" , "GET, POST, PUT, DELETE" )
w . Header (). Set ( "Access-Control-Allow-Headers" , "Content-Type, Authorization" )
// Handle preflight
if r . Method == "OPTIONS" {
w . WriteHeader ( http . StatusOK )
return
}
h . ServeHTTP ( w , r )
})
})
}
Request ID Injection
Similar to TraceID, but from header if present:
func RequestIDMiddleware ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
// Try to get from header
requestID := r . Header . Get ( "X-Request-ID" )
if requestID == "" {
requestID = uuid . NewString ()
}
// Add to context
ctx := context . WithValue ( r . Context (), requestIDKey , requestID )
r = r . WithContext ( ctx )
// Echo back in response
w . Header (). Set ( "X-Request-ID" , requestID )
h . ServeHTTP ( w , r )
})
}
Middleware with Configuration
Create configurable middleware using constructors:
type RateLimitMiddleware struct {
requestsPerMinute int
limiter * rate . Limiter
}
func NewRateLimitMiddleware ( requestsPerMinute int ) * RateLimitMiddleware {
return & RateLimitMiddleware {
requestsPerMinute : requestsPerMinute ,
limiter : rate . NewLimiter ( rate . Limit ( requestsPerMinute ), requestsPerMinute ),
}
}
func ( m * RateLimitMiddleware ) Wrap ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if ! m . limiter . Allow () {
http . Error ( w , "rate limit exceeded" , http . StatusTooManyRequests )
return
}
h . ServeHTTP ( w , r )
})
}
Usage:
rateLimiter := NewRateLimitMiddleware ( 100 )
api . Use ( rateLimiter )
Context-Based Middleware
Pass values through request context:
type contextKey string
const userKey contextKey = "user"
// Set in middleware
func SetUser ( ctx context . Context , user * User ) context . Context {
return context . WithValue ( ctx , userKey , user )
}
// Get in handler
func GetUser ( ctx context . Context ) ( * User , bool ) {
user , ok := ctx . Value ( userKey ).( * User )
return user , ok
}
// Middleware
func ( m * AuthMiddleware ) Wrap ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
user := m . authenticate ( r )
ctx := SetUser ( r . Context (), user )
r = r . WithContext ( ctx )
h . ServeHTTP ( w , r )
})
}
// Handler
func MyHandler ( w http . ResponseWriter , r * http . Request ) {
user , ok := GetUser ( r . Context ())
if ! ok {
http . Error ( w , "unauthorized" , http . StatusUnauthorized )
return
}
// Use user...
}
Conditional Middleware
Apply middleware based on conditions:
func ConditionalAuthMiddleware ( publicPaths [] string ) httpserver . Middleware {
return httpserver . MiddlewareFunc ( func ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
// Skip auth for public paths
for _ , path := range publicPaths {
if r . URL . Path == path {
h . ServeHTTP ( w , r )
return
}
}
// Require auth for other paths
if ! isAuthenticated ( r ) {
http . Error ( w , "unauthorized" , http . StatusUnauthorized )
return
}
h . ServeHTTP ( w , r )
})
})
}
Combining Middleware
Chain multiple middleware together:
// Create a combined middleware stack
func StandardMiddleware () [] httpserver . Middleware {
return [] httpserver . Middleware {
log . NewTraceIDMiddleware ( nil , "" ),
RecoveryMiddleware ,
LoggingMiddleware ,
}
}
// Apply to server
api := httpserver . New ( "8080" , 5 * time . Second )
for _ , mw := range StandardMiddleware () {
api . Use ( mw )
}
Testing Middleware
Test middleware in isolation:
func TestAuthMiddleware ( t * testing . T ) {
// Create test handler
testHandler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusOK )
w . Write ([] byte ( "success" ))
})
// Wrap with middleware
middleware := NewAuthMiddleware ( mockAuthService )
wrappedHandler := middleware . Wrap ( testHandler )
// Create test request
req := httptest . NewRequest ( "GET" , "/test" , nil )
req . AddCookie ( & http . Cookie { Name : "session" , Value : "valid-token" })
// Record response
rec := httptest . NewRecorder ()
// Execute
wrappedHandler . ServeHTTP ( rec , req )
// Assert
if rec . Code != http . StatusOK {
t . Errorf ( "expected status 200, got %d " , rec . Code )
}
}
Best Practices
Place middleware that sets context values (like trace ID, user) before middleware that uses those values (like logging).
Unless you’re intentionally short-circuiting the request (auth failure, rate limit), always call h.ServeHTTP(w, r).
Don't Modify Request Body
Reading r.Body consumes it. If you need to inspect the body, create a copy first using io.TeeReader.
Each middleware should do one thing well. Compose multiple middleware instead of creating monolithic ones.
Use Types for Configuration
Create a struct type for middleware with configuration rather than passing many parameters.
Next Steps
HTTP Routing Learn how to apply middleware to routes and groups
Domains See how middleware fits into domain architecture
Logging Use context-aware logging in middleware