BudiBadu Logo

Samplebadu

Code with Example
BudiBadu Logo
Samplebadu

Go by Example: Custom Middleware

Go 1.23

Understand the Middleware pattern in Go web development. This example shows how to create custom middleware to wrap HTTP handlers, enabling cross-cutting concerns like logging, authentication, and error handling to be applied consistently.

Code

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

// Middleware function
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        // Call the next handler
        next.ServeHTTP(w, r)
        
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", hello)

    // Wrap the mux with the middleware
    wrappedMux := loggingMiddleware(mux)

    fmt.Println("Server starting on :8080")
    http.ListenAndServe(":8080", wrappedMux)
}

Explanation

Middleware allows you to wrap HTTP handlers to execute code before and after the main handler logic. This is ideal for cross-cutting concerns like logging, authentication, and panic recovery.

The Middleware Pattern:

  • Signature: func(http.Handler) http.Handler. This allows middleware to be chained together (A wraps B wraps C).
  • Control Flow: Code before next.ServeHTTP runs on the request way in. Code after it runs on the response way out.
  • Context: Middleware often adds data to the request context (e.g., User ID) for downstream handlers to use.

Code Breakdown

11
The middleware signature: it takes an http.Handler (the 'next' handler) and returns a new http.Handler. This is the standard pattern for Go middleware.
12
We return an http.HandlerFunc, which is a function type that implements the http.Handler interface. This allows us to define the handler logic inline using an anonymous function.
13
Code here runs BEFORE the request is handled. We record the start time to calculate latency later.
16
next.ServeHTTP(w, r) passes control to the next handler in the chain. This is crucial; without it, the request would stop here.
18
Code here runs AFTER the request has been handled. We log the method, path, and duration. This is how we can measure response times.
27
http.NewServeMux creates a new request multiplexer. In a real app, you might use a router like chi or gorilla/mux, but ServeMux is sufficient for simple cases.
31
We wrap the entire mux with our loggingMiddleware. This ensures that EVERY request handled by this mux goes through the logger first.