Go > Web Development > REST APIs > Middleware functions
Simple Logging Middleware in Go
This snippet demonstrates how to create a simple logging middleware function in Go for a REST API. This middleware logs each incoming request's method and path to the console. Middleware functions are essential for handling cross-cutting concerns like logging, authentication, and authorization in a structured way.
Concepts Behind Middleware
Middleware functions in Go's `net/http` package are functions that sit between your server's request handling logic and the incoming HTTP requests. They allow you to pre-process requests before they reach your handlers and post-process responses before they are sent back to the client. This provides a powerful mechanism to add functionality to your API without modifying individual handler functions, promoting code reusability and maintainability.
Middleware Function Definition
This code defines the `LoggerMiddleware` function. It takes an `http.Handler` as input (which is the next handler in the chain) and returns a new `http.Handler`. Inside the returned handler function (an anonymous function that implements `http.HandlerFunc`), we log the request's method and URI. Finally, we call `next.ServeHTTP(w, r)` to pass the request to the next handler in the chain.
package main
import (
"fmt"
"log"
"net/http"
)
// LoggerMiddleware is a middleware function that logs the request method and URI.
func LoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Log the request details.
log.Printf("Request: %s %s", r.Method, r.RequestURI)
// Call the next handler in the chain.
next.ServeHTTP(w, r)
})
}
Applying the Middleware
In the `main` function, we first define a simple handler that responds with "Hello, World!". Then, we wrap this handler with the `LoggerMiddleware`. This creates a new handler that first executes the logging logic and then calls the original handler. Finally, we register the wrapped handler with the `/` route and start the HTTP server. Note how `http.Handle` is used to register our middleware. `http.ListenAndServe` will use this registered middleware.
func main() {
// Define a simple handler.
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
})
// Wrap the handler with the logger middleware.
loggedHandler := LoggerMiddleware(handler)
// Register the wrapped handler.
http.Handle("/", loggedHandler)
// Start the server.
log.Fatal(http.ListenAndServe(":8080", nil))
}
Complete Example
This is a complete runnable example. You can copy and paste this into a `main.go` file and run it using `go run main.go`. Accessing `http://localhost:8080` will print "Hello, World!" in the browser and log the request details in the console.
package main
import (
"fmt"
"log"
"net/http"
)
// LoggerMiddleware is a middleware function that logs the request method and URI.
func LoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Log the request details.
log.Printf("Request: %s %s", r.Method, r.RequestURI)
// Call the next handler in the chain.
next.ServeHTTP(w, r)
})
}
func main() {
// Define a simple handler.
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
})
// Wrap the handler with the logger middleware.
loggedHandler := LoggerMiddleware(handler)
// Register the wrapped handler.
http.Handle("/", loggedHandler)
// Start the server.
log.Fatal(http.ListenAndServe(":8080", nil))
}
Real-Life Use Case
Imagine a REST API endpoint that handles user authentication. A middleware could be used to verify the authentication token sent in the request headers before passing the request to the handler. This ensures that only authenticated users can access the endpoint.
Best Practices
Interview Tip
When asked about middleware in an interview, be prepared to explain its purpose, how it works in Go's `net/http` package, and provide examples of common use cases like logging, authentication, and request validation. Also, highlight the benefits of using middleware in terms of code reusability and maintainability.
When to Use Middleware
Use middleware when you have functionalities that need to be applied to multiple endpoints in your API. This avoids code duplication and makes your code more modular and easier to maintain. Common use cases include:
Memory Footprint
The memory footprint of middleware is generally small. The primary overhead comes from the function calls and any additional data stored in the request context. Careful consideration should be given to the amount of data stored in the context to avoid unnecessary memory consumption.
Alternatives
Instead of using middleware, you could implement the same logic directly within each handler function. However, this approach leads to code duplication and makes it harder to maintain the code. Another alternative is to use an interceptor pattern (often used in gRPC), but middleware is the standard approach in HTTP.
Pros
Cons
FAQ
-
What is the purpose of `http.Handler` and `http.HandlerFunc`?
`http.Handler` is an interface that defines the `ServeHTTP` method, which is used to handle HTTP requests. `http.HandlerFunc` is a type adapter that allows ordinary functions to be used as HTTP handlers. -
How do I pass data between middleware functions?
Use the `context` package to store request-scoped values. The `context` can be accessed and modified by middleware functions and handlers. -
Can I modify the request or response in a middleware function?
Yes, you can modify the request or response in a middleware function. For example, you can add headers to the response or modify the request body.