Middleware¶
Middleware wraps handlers to add cross-cutting functionality like logging, authentication, and rate limiting.
How Middleware Works¶
Middleware is a function that takes a handler and returns a new handler:
The middleware can:
- Execute code before the handler
- Call the next handler
- Execute code after the handler
- Short-circuit and return early
func MyMiddleware(next marten.Handler) marten.Handler {
return func(c *marten.Ctx) error {
// Before handler
log.Println("Before")
// Call next handler
err := next(c)
// After handler
log.Println("After")
return err
}
}
Using Middleware¶
Global Middleware¶
Applied to all routes:
app := marten.New()
app.Use(middleware.Logger)
app.Use(middleware.Recover)
// Or multiple at once
app.Use(
middleware.Logger,
middleware.Recover,
middleware.CORS(middleware.DefaultCORSConfig()),
)
Group Middleware¶
Applied to routes in a group:
api := app.Group("/api")
api.Use(authMiddleware)
api.GET("/users", listUsers) // Has authMiddleware
api.POST("/users", createUser) // Has authMiddleware
Route-Specific Middleware¶
Applied to a single route:
Execution Order¶
Middleware executes in the order it's added:
app.Use(mw1, mw2, mw3)
app.GET("/", handler)
// Request flow:
// mw1 -> mw2 -> mw3 -> handler -> mw3 -> mw2 -> mw1
sequenceDiagram
participant R as Request
participant M1 as Middleware 1
participant M2 as Middleware 2
participant H as Handler
R->>M1: Before
M1->>M2: Before
M2->>H: Execute
H-->>M2: Return
M2-->>M1: After
M1-->>R: Response Writing Custom Middleware¶
Basic Pattern¶
func Timer(next marten.Handler) marten.Handler {
return func(c *marten.Ctx) error {
start := time.Now()
err := next(c)
duration := time.Since(start)
c.Header("X-Response-Time", duration.String())
return err
}
}
With Configuration¶
type RateLimitConfig struct {
Requests int
Window time.Duration
}
func RateLimit(cfg RateLimitConfig) marten.Middleware {
// Setup
limiter := newLimiter(cfg)
return func(next marten.Handler) marten.Handler {
return func(c *marten.Ctx) error {
if !limiter.Allow(c.ClientIP()) {
return c.JSON(429, marten.E("rate limit exceeded"))
}
return next(c)
}
}
}
// Usage
app.Use(RateLimit(RateLimitConfig{
Requests: 100,
Window: time.Minute,
}))
Short-Circuit¶
Return early without calling the next handler:
func Auth(next marten.Handler) marten.Handler {
return func(c *marten.Ctx) error {
token := c.Bearer()
if token == "" {
return c.Unauthorized("missing token")
}
user, err := validateToken(token)
if err != nil {
return c.Unauthorized("invalid token")
}
c.Set("user", user)
return next(c) // Only called if auth succeeds
}
}
Error Handling¶
func ErrorHandler(next marten.Handler) marten.Handler {
return func(c *marten.Ctx) error {
err := next(c)
if err != nil {
// Log the error
log.Printf("Error: %v", err)
// Return appropriate response
var validationErr *ValidationError
if errors.As(err, &validationErr) {
return c.BadRequest(validationErr.Message)
}
return c.ServerError("internal error")
}
return nil
}
}
Built-in Middleware¶
Marten includes 12 production-ready middleware:
| Middleware | Purpose |
|---|---|
| Logger | Request logging |
| Recover | Panic recovery |
| CORS | Cross-origin requests |
| RateLimit | Rate limiting |
| BasicAuth | Basic authentication |
| Timeout | Request timeouts |
| Secure | Security headers |
| BodyLimit | Request size limits |
| Compress | Gzip compression |
| ETag | Response caching |
| RequestID | Request tracking |
| NoCache | Cache prevention |
See Middleware Reference for details.
Common Patterns¶
Authentication¶
func JWTAuth(next marten.Handler) marten.Handler {
return func(c *marten.Ctx) error {
token := c.Bearer()
if token == "" {
return c.Unauthorized("missing token")
}
claims, err := jwt.Validate(token)
if err != nil {
return c.Unauthorized("invalid token")
}
c.Set("user_id", claims.UserID)
c.Set("email", claims.Email)
return next(c)
}
}
Role-Based Access¶
func RequireRole(role string) marten.Middleware {
return func(next marten.Handler) marten.Handler {
return func(c *marten.Ctx) error {
userRole := c.GetString("role")
if userRole != role {
return c.Forbidden("insufficient permissions")
}
return next(c)
}
}
}
// Usage
admin := app.Group("/admin")
admin.Use(JWTAuth, RequireRole("admin"))
Request Validation¶
func ValidateJSON(next marten.Handler) marten.Handler {
return func(c *marten.Ctx) error {
if c.Request.ContentLength > 0 && !c.IsJSON() {
return c.BadRequest("content-type must be application/json")
}
return next(c)
}
}
Metrics¶
func Metrics(next marten.Handler) marten.Handler {
return func(c *marten.Ctx) error {
start := time.Now()
err := next(c)
duration := time.Since(start)
status := c.StatusCode()
metrics.RecordRequest(c.Method(), c.Path(), status, duration)
return err
}
}
Best Practices¶
1. Keep Middleware Focused¶
Each middleware should do one thing:
// Good - single responsibility
app.Use(middleware.Logger)
app.Use(middleware.Recover)
app.Use(middleware.CORS(cfg))
// Avoid - doing too much in one middleware
app.Use(logRecoverCORSMiddleware)
2. Order Matters¶
Put middleware in logical order:
app.Use(
middleware.RequestID, // First: assign ID for tracking
middleware.Logger, // Second: log with ID
middleware.Recover, // Third: catch panics
middleware.RateLimit, // Fourth: reject excess requests early
middleware.Auth, // Fifth: authenticate
)
3. Don't Modify Request After Handler¶
// Good
func Middleware(next marten.Handler) marten.Handler {
return func(c *marten.Ctx) error {
// Modify request before
c.Set("start_time", time.Now())
err := next(c)
// Read values after, but don't modify request
start := c.Get("start_time").(time.Time)
log.Printf("Duration: %v", time.Since(start))
return err
}
}