Middleware
Introduction
Info
Middleware performs some specific function on the HTTP request or response at a specific stage in the HTTP pipeline before or after the user defined controller. Middleware is a design pattern to eloquently add cross cutting concerns like logging, handling authentication without having many code contact points.
Typical middleware use cases:
- Authentication
- Metrics Collections
- Rate Limiting
- Request/Response Modification
- Tracing and Observability
In triad middleware has signature func(next triad.HandlerFunc) triad.HandlerFunc internally expands to func(next func(http.ResponseWriter, *Request) error) func(http.ResponseWriter, *Request) error. Triad middleware are fully compatible with net/http ecosystem. Any middleware implemented as func(next http.Handler) http.Handler can be used through compatibility layer. The main difference is triad's middleware participates in error propagation model. If any handler or middleware returns an error, Triad automatically invokes the configured error handler.
Creating middleware
The example below stores a user ID in the request context and makes it available to downstream handlers.
// http middleware setting value on request context
func UserMiddleware(next triad.HandlerFunc) triad.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
// create a request from exisiting request context pass the required
// key and value pair.
r = r.WithContext(context.WithValue(r.Context(), "user", "123"))
// call the next handler in call chain passing newly created request
// and response object. value set in request context will be accessible.
return next(w, r)
}
}
// Handler for fetching and sending middleware value set in call chain context
func UserHandler(w http.ResponseWriter, r *http.Request) error {
// fetch the value from request context set in
// UserMiddleware Above.
user, ok := r.Context().Value("user").(string)
if !ok {
return errors.New("unexpected value type received")
}
// respond to client
return triad.Text(w, "hi "+user, http.StatusOK)
}
Middleware execution order follows the registration chain, and any values added to the request context become available to subsequent middleware and handlers.
Using standard library middleware
Warning
Standard net/http middleware does not support Triad's error-returning handler model. Any errors must be handled and written to the response inside the middleware itself.
To register a standard library middleware, wrap it with triad.CompatMiddleware.
// Standard stdlib middleware which logs request data
func Logger(next http.Handler) http.Handler {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
rw := &respWriter{ResponseWriter: w}
next.ServeHTTP(w, r)
end := time.Since(start)
slog.Info(
"request received",
slog.String("method", r.Method),
slog.String("path", r.URL.Path),
slog.String("remote_address", r.RemoteAddr),
slog.Int("status_code", rw.code),
slog.String("latency", end.String()),
)
}
}
func main() {
r := triad.New()
// Registering using Compatibility layer
r.Use(traid.CompatMiddleware(Logger))
r.Get("/", func(w http.ResponseWriter, r *http.Request) error {
return triad.Text(w, "root handler", http.StatusOK)
})
}
Middleware registration
Middleware must be registered before any routes are added to the router scope where the middleware applies.
This rule exists because route middleware chains are finalized when a route is registered.
Root Router → Register global middleware before any routes:
InvalidInline Group(With) →
Middleware added through With() is applied only to routes registered through that derived router.
r := triad.New()
r.Use(GlobalMiddleware)
api := r.With(AuthMiddleware)
api.Get("/profile", ProfileHandler)
r := traid.New()
api := r.With(AuthMiddleware)
r.Use(GlobalMiddleware) // invalid
api.Get("/profile", ProfileHandler)
api will not inherit middleware that is registered on the parent router after With() is created.
Route Group → Middleware for a group must be registered before any routes inside that group.
r := triad.New()
r.Group("/admin", func(g *triad.Triad) {
g.Use(AdminMiddleware)
g.Get("/", DashboardHandler)
})
r := triad.New()
r.Group("/admin", func(g *triad.Triad) {
g.Get("/", DashboardHandler)
g.Use(AdminMiddleware) // invalid
})
Middleware Hierarchy
Middleware inherited from parent scopes.
r.Use(GlobalMiddleware)
r.Get("/", GlobalHandler)
r.With(AuthMiddleware).Get("/profile", ProfileHandler)
r.Group("/admin", func(g *triad.Triad) {
g.Use(AdminMiddleware)
g.Get("/", DashboardHandler)
})
Request Flow →