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
go
package handlers type ListFilters struct { Status string `form:"status" binding:"omitempty,oneof=active archived all"`
Cross-field query validation in Gin
validation
struct-tags
query-binding
Intermediate
9 steps
php
<?php final class MarkdownParser {
Building a small Markdown-to-HTML parser in PHP
state-machine
parsing
closures
Intermediate
10 steps
typescript
import { CallHandler, ExecutionContext, Injectable,
Recording HTTP metrics with a NestJS interceptor
interceptor
observability
prometheus
Intermediate
5 steps
go
package editor import ( "context"
How a debouncer coalesces bursts in Go
debounce
concurrency
timers
Intermediate
8 steps
javascript
function initScrollSpy() { const links = Array.from(document.querySelectorAll('.nav a[href^="#"]')); const sections = links .map((link) => document.querySelector(link.getAttribute('href')))
Building a scroll spy with IntersectionObserver
intersectionobserver
dom
event-driven
Intermediate
7 steps
python
from typing import Callable, Dict, Type class PluginRegistry:
A decorator-based plugin registry in Python
decorators
registry pattern
factory
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.