Understanding Middleware in Go REST APIs
What is Middleware?
Middleware in the context of Go web applications acts as an intermediary layer between the incoming HTTP request and your application's handlers.
It intercepts HTTP requests before they reach your final handler, allowing you to perform operations on the request or response.
Essentially, middleware is an implementation of the http.Handler interface that wraps another handler, executing code before and/or after the handler is called.
Basic Example
package main
import (
"log"
"net/http"
"time"
)
func main() {
// Create our handler
handler := http.HandlerFunc(helloHandler)
// Wrap it with the middleware
wrappedHandler := loggingMiddleware(handler)
// Register the wrapped handler
http.Handle("/", wrappedHandler)
// Start the server
log.Println("Server starting on port 8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Code executed before the handler
start := time.Now()
log.Printf("Started %s %s", r.Method, r.URL.Path)
// Call the wrapped handler
next.ServeHTTP(w, r)
// Code executed after the handler
log.Printf("Completed %s %s in %v", r.Method, r.URL.Path, time.Since(start))
})
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}
In this example, loggingMiddleware is a function that takes an http.Handler and returns a new http.Handler that wraps the original one.
It logs the start time of the request, calls the original handler, and then logs how long the request took to process.
Chaining Middleware
Middleware chaining is simple but powerful. It's like passing a request through a series of checkpoints before reaching its destination:
func main() {
// Our core handler function
handler := http.HandlerFunc(helloHandler)
// Apply middleware in sequence
handler = loggingMiddleware(handler)
handler = authMiddleware(handler)
handler = timeoutMiddleware(handler)
http.Handle("/", handler)
http.ListenAndServe(":8080", nil)
}
In this example, the middleware execution happens in the opposite order of how they're applied:
- When a request comes in, it first goes through timeoutMiddleware
- Then it passes through authMiddleware
- Next it goes through loggingMiddleware
- Finally, it reaches the helloHandler
This happens because each middleware wraps the next one in the chain, creating nested layers.
Understanding this execution order is crucial when designing your middleware chain, especially when certain middleware depends on the processing done by others.
Common Use Cases
Some common use cases include:
- Logging: Recording request details, response times, and errors for monitoring and debugging.
- Authentication and Authorization: Verifying user identity and checking if they have permission to access certain resources.
- Request Validation: Ensuring requests contain required fields or meet specific criteria before they reach your handlers.
- CORS (Cross-Origin Resource Sharing): Managing which domains can access your API.
- Rate Limiting: Preventing abuse by limiting the number of requests from a single client.
- Response Compression: Automatically compressing HTTP responses to reduce bandwidth.
- Error Handling: Providing consistent error responses across your API.
- Content Type Negotiation: Processing requests based on their content type and formatting responses accordingly.
- Request Parsing: Automatically parsing JSON or form data into structured types.
- Caching: Storing response data to improve performance for frequent requests.