go
54 lines · 8 steps
Composing HTTP middleware in Go
A middleware type and a Chain helper let you wrap an http.Handler in reusable layers for logging and auth.
Explained by
highlit
1package server
2
3import (
4 "context"
5 "log"
6 "net/http"
7 "time"
8)
9
10type Middleware func(http.Handler) http.Handler
11
12func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
13 for i := len(middlewares) - 1; i >= 0; i-- {
14 h = middlewares[i](h)
15 }
16 return h
17}
18
19func Logging(logger *log.Logger) Middleware {
20 return func(next http.Handler) http.Handler {
21 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22 start := time.Now()
23 rw := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
24 next.ServeHTTP(rw, r)
25 logger.Printf("%s %s %d %s", r.Method, r.URL.Path, rw.status, time.Since(start))
26 })
27 }
28}
29
30func RequireAuth(next http.Handler) http.Handler {
31 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
32 token := r.Header.Get("Authorization")
33 if token == "" {
34 http.Error(w, "unauthorized", http.StatusUnauthorized)
35 return
36 }
37 ctx := context.WithValue(r.Context(), userKey, token)
38 next.ServeHTTP(w, r.WithContext(ctx))
39 })
40}
41
42type statusRecorder struct {
43 http.ResponseWriter
44 status int
45}
46
47func (r *statusRecorder) WriteHeader(code int) {
48 r.status = code
49 r.ResponseWriter.WriteHeader(code)
50}
51
52type contextKey string
53
54const userKey contextKey = "user"
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Middleware is just a function that takes a handler and returns a wrapped handler, so layers compose freely.
- 2Wrapping the ResponseWriter is the standard trick for observing status codes the handler sets.
- 3context.WithValue passes per-request data down the chain without changing handler signatures.
Related explainers
rust
use axum::{ http::StatusCode, response::IntoResponse, routing::get,
Serving an SPA and API with Axum
routing
static-files
spa-fallback
Intermediate
7 steps
go
func RecoveryHandler(logger *zap.Logger) gin.HandlerFunc { return gin.CustomRecovery(func(c *gin.Context, recovered any) { var brokenPipe bool if ne, ok := recovered.(*net.OpError); ok {
A panic-recovery middleware in Gin
middleware
panic-recovery
structured-logging
Intermediate
6 steps
ruby
def build_tree(rows, root_id: nil) children_by_parent = Hash.new { |hash, key| hash[key] = [] } rows.each do |row|
Building a tree from flat rows in Ruby
recursion
hash-grouping
tree-structure
Intermediate
5 steps
javascript
const asyncHandler = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); };
Async error handling in Express routes
async-await
error-handling
middleware
Intermediate
7 steps
rust
use std::time::Duration; use axum::{ http::{header, HeaderValue, Method},
Configuring CORS on an Axum Router
cors
middleware
http-headers
Intermediate
7 steps
php
<?php namespace App\Http\Controllers;
Building a filtered product index in Laravel
query-builder
validation
conditional-queries
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/composing-http-middleware-in-go-explained-go-7ad1/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.