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.

360 Tech Engineering
360 Tech Engineering
360 Tech Engineering
Writing Middleware in Go: A Practical Guide

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendWebmiddlewareprogrammingGoHTTP
360 Tech Engineering
Written by

360 Tech Engineering

Official tech channel of 360, building the most professional technology aggregation platform for the brand.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.