{
"$type": "site.standard.document",
"canonicalUrl": "https://rednafi.com/go/middleware-vs-delegation/",
"description": "Compare middleware stacking with embedded delegation in Go HTTP servers. Learn when to override ServeHTTP for simpler request handling.",
"path": "/go/middleware-vs-delegation/",
"publishedAt": "2025-03-06T00:00:00.000Z",
"site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
"tags": [
"Go",
"API",
"Web"
],
"textContent": "Middleware is usually the go-to pattern in Go HTTP servers for tweaking request behavior.\nTypically, you wrap your base handler with layers of middleware - one might log every\nrequest, while another intercepts specific routes like /special to serve a custom\nresponse.\n\nHowever, I often find the indirections introduced by this pattern a bit hard to read and\ndebug. I recently came across the embedded delegation pattern while browsing [Gin's HTTP\nrouter source code]. Here, I explore both patterns and explain why I usually start with\ndelegation whenever I need to modify HTTP requests in my Go services.\n\nMiddleware stacking\n\nHere's an example where the logging middleware records each request, and the special\nmiddleware intercepts requests to /special:\n\nIn this setup, every incoming request is first handled by the special middleware, which\nchecks for the /special route, and then by the logging middleware that logs the request\ndetails. We're effectively stacking the middleware functions.\n\nIf you hit the server with:\n\nthe server logs will look like this:\n\nStacking middleware functions like middleware3(middleware2(middleware1(mux))) can get\nmessy when you have many of them. That's why people usually write a wrapper function to\napply the middlewares to the mux:\n\napplyMiddleware takes an http.Handler and a variadic list of middleware functions\n(...func(http.Handler) http.Handler). It loops over the middleware in reverse order so\neach one wraps the next properly. This avoids deep nesting like\nmiddleware3(middleware2(middleware1(mux))) and keeps the middleware chain tidy.\n\nYou'd then use it like this:\n\nThis behaves just like the manual middleware stacking, but it's a bit cleaner.\n\nWhile this is the canonical way to handle request-response modifications in Go, it can\nsometimes be hard to reason about, especially when debugging or dealing with many middleware\nlayers.\n\nThere's another way to achieve the same result without dealing with a soup of nested\nfunctions. The next section talks about that.\n\nEmbedded delegation\n\nEmbedded delegation (or the delegation pattern) means you embed the standard HTTP\nmultiplexer inside your own struct and override its ServeHTTP method.\n\nIt's a bit like inheritance - overriding a method in a subclass to add extra functionality\nand then delegating the call to the original method. Although Go doesn't have a class\nhierarchy, you can still delegate responsibilities to the embedded type's method.\n\nThe following example implements the same behavior - logging every request and intercepting\nthe /special route - directly within a custom mux:\n\nIn this example, the custom mux centralizes both logging and special-case route handling\nwithin one ServeHTTP method. This approach cuts out the extra function calls in a\nmiddleware chain and can simplify tracking the request flow. I find it a bit easier on the\neyes too.\n\nIf you have a bunch of extra functionality to add inside cm.ServeHTTP, you can wrap them\nin utility functions like this:\n\nThen, simply call these functions inside your cm.ServeHTTP method:\n\nThis keeps all the request modifications in a single ServeHTTP method.\n\nMixing the two approaches\n\nYou can also mix both techniques. For example, you might use direct delegation for special\nroute handling and then wrap the resulting handler with middleware for logging. Here's how a\nhybrid solution might look:\n\nIn this hybrid approach, the specialized behavior (intercepting the /special path) is\nhandled via direct delegation, while logging stays modular as middleware. This gives you the\nbest of both worlds.\n\nI usually start with the embedded delegation and gradually introduce the middleware pattern\nif I need it later. It's easier to adopt the middleware pattern if you start with delegation\nthan the other way around.\n\n\n\n\n[gin's http router source code]:\n https://github.com/gin-gonic/gin/blob/3b28645dc95d58e0df36b8aff7a6c64f7c0ca5e9/gin.go#L94",
"title": "Stacked middleware vs embedded delegation in Go"
}