Writing Middleware in Go: A Practical Guide
This article explains how to create HTTP middleware in Go, covering request reading, trailing‑slash redirection, request‑ID injection, server header manipulation, custom ResponseWriter implementation, and handling of additional interfaces such as Push and Flush, with complete code examples.
Many developers wonder how to write middleware in Go; this guide demonstrates the process step by step.
Reading the request
All middleware receives an http.Handler and returns an http.Handler. A basic wrapper looks like:
func X(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Something here...
h.ServeHTTP(w, r)
})
}Trailing‑slash redirection
To redirect URLs that end with a slash to their non‑slash equivalents:
func TrailingSlashRedirect(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" && r.URL.Path[len(r.URL.Path)-1] == '/' {
http.Redirect(w, r, r.URL.Path[:len(r.URL.Path)-1], http.StatusMovedPermanently)
return
}
h.ServeHTTP(w, r)
})
}Injecting a request ID
Since the request object should not be mutated directly, create a shallow copy and set a new header:
func RequestID(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r2 := new(http.Request)
*r2 = *r
r2.Header.Set("X-Request-Id", uuid.NewV4().String())
h.ServeHTTP(w, r2)
})
}Setting a server header
To add or replace the Server response header, wrap the handler with a custom writer:
func Server(h http.Handler, servername string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", servername)
h.ServeHTTP(w, r)
})
}When the inner handler also sets the header, the outer header may be overwritten. Implement a custom ResponseWriter to control when the header is written:
type serverWriter struct {
w http.ResponseWriter
name string
wroteHeader bool
}
func (s *serverWriter) Header() http.Header { return s.w.Header() }
func (s *serverWriter) WriteHeader(code int) {
if !s.wroteHeader {
s.w.Header().Set("Server", s.name)
s.wroteHeader = true
}
s.w.WriteHeader(code)
}
func (s *serverWriter) Write(b []byte) (int, error) {
if !s.wroteHeader {
s.w.Header().Set("Server", s.name)
s.wroteHeader = true
}
return s.w.Write(b)
}Use this writer in the middleware:
func Server(h http.Handler, servername string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sw := &serverWriter{w: w, name: servername}
h.ServeHTTP(sw, r)
})
}Other ResponseWriter methods
The ResponseWriter may also implement interfaces like http.Pusher or http.Flusher. Forward those calls when the underlying writer supports them:
// Push implements http.Pusher
func (s *serverWriter) Push(target string, opts *http.PushOptions) error {
if pusher, ok := s.w.(http.Pusher); ok {
return pusher.Push(target, opts)
}
return http.ErrNotSupported
}
// Flush implements http.Flusher
func (s *serverWriter) Flush() {
if f, ok := s.w.(http.Flusher); ok {
f.Flush()
}
}Conclusion
After studying these examples, you should have a solid understanding of how to build reusable, composable middleware in Go and can start creating your own to handle logging, authentication, tracing, or any other cross‑cutting concerns.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
360 Tech Engineering
Official tech channel of 360, building the most professional technology aggregation platform for the brand.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
