# net/http and the Middleware Pattern

> Every Go web framework sits on net/http: http.Handler and ServeHTTP are the whole abstraction, and middleware is a closure wrapping a Handler, outermost first.

Source: https://sheng.page/en/posts/go-net-http-and-middleware/ · Published: 2025-01-19 · Author: Sheng

Strip any Go web framework far enough and you hit `net/http`. Once `http.Handler` and `ServeHTTP` click, you can see what a framework adds on top, and what it hides.

## http.Handler and ServeHTTP

The whole net/http server abstraction is a single interface:

```go
type Handler interface {
    ServeHTTP(w http.ResponseWriter, r *http.Request)
}
```

Anything with a `ServeHTTP` method is a Handler — `http.ServeMux`, third-party frameworks, your own handlers, all of them. The common shortcut is `http.HandlerFunc`: a function type carrying a `ServeHTTP` method, so a plain function can be a Handler without a struct:

```go
type HandlerFunc func(http.ResponseWriter, *http.Request)

func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    f(w, r) // the method just calls itself
}
```

"A method on a function type" is the Go idiom the whole middleware pattern rests on.

## Middleware is a Handler wrapping a Handler

Middleware is no magic; it's a function that takes a Handler and returns one:

```go
func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}
```

The signature `func(http.Handler) http.Handler` is the entire pattern. It injects behaviour around the call to `next` — one wrap, one responsibility.

## Chaining with closures

To stack several, wrap one inside another. A closure-based helper:

```go
func Chain(h http.Handler, mws ...func(http.Handler) http.Handler) http.Handler {
    for i := len(mws) - 1; i >= 0; i-- {
        h = mws[i](h) // wrap in reverse so the first listed runs outermost
    }
    return h
}

handler := Chain(mux, Logging, Auth) // a request hits Logging, then Auth
```

It's an onion: the outermost layer runs first. A framework (gortex included) just generalises this and bolts routing and a context onto it.

## defer and recover

A panic in a handler takes down that request's goroutine by default. `defer` + `recover` turns it into a 500 so one request can't crash the server:

```go
func Recover(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                http.Error(w, "internal error", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}
```

`recover` only works when called inside a deferred function. `defer` runs on function exit — including panic unwinding — so it's perfectly placed to catch it.

## From Effective Go: Functions, defer

The middleware pattern feeds on Go treating functions as first-class values: pass them, return them, store them in a slice. Effective Go's [Functions](https://go.dev/doc/effective_go#functions) also covers multiple return values and named results.

`defer` is the cleanup idiom Effective Go singles out: schedule "close the file, unlock the mutex, finish up" at the function's exit, written next to where you opened the resource so no early `return` can skip it. Two things to remember: multiple defers run LIFO, and arguments are evaluated at the `defer` statement, not when the call finally runs.


## Notes

- net/http's core is one interface: `http.Handler` / `ServeHTTP`, and every framework is built on it.
- Middleware is just `func(http.Handler) http.Handler`; one wrap, one behaviour.
- Chain with closures; the order is an onion, outermost first.
- `defer` + `recover` turns a panic into a 500; don't let one request topple the server.
- Next up, [Struct Tags and reflect](/en/posts/go-struct-tags-and-reflection/) lays the groundwork for declarative routing.

Further reading: the series goes on to dissect the gortex framework built on these ideas — source at [yshengliao/gortex](https://github.com/yshengliao/gortex).

---

> Ideas and technical judgement by Sheng; drafted with Claude · Go 1.23 (latest release at the time) · compiled retroactively · part of the 2026-06-13 blog renovation, paint still drying.