Go > Web Development > HTTP Basics > Routing with mux
Advanced Routing with Mux and Middleware
This example expands on the basic routing concept by demonstrating how to define multiple, more complex routes, including those with optional parameters and regular expressions. It also introduces the concept of middleware to perform actions on every request.
Import Necessary Packages
Similar to the previous example, this imports the required packages.
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
"log"
)
Define Middleware
This function defines a simple middleware that logs each incoming request's method and path. Middleware functions take an `http.Handler` as input and return a modified `http.Handler`. The `next.ServeHTTP(w, r)` call passes the request to the next handler in the chain.
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Request received: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
Define Route Handlers
These are handlers for different routes, using route parameters and handling optional URL query parameters. `productHandler` handles `/products/{id}` and optionally a `discount` query parameter. `categoryHandler` handles `/categories/{category}`.
func productHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
productID := vars["id"]
fmt.Fprintf(w, "Viewing product with ID: %s\n", productID)
//Example optional query parameter
discount := r.URL.Query().Get("discount")
if discount != "" {
fmt.Fprintf(w, "Discount applied: %s\n", discount)
}
}
func categoryHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
categoryName := vars["category"]
fmt.Fprintf(w, "Viewing products in category: %s\n", categoryName)
}
Create the Router, Define Routes, and Apply Middleware
This `main` function creates a `mux.Router`, defines routes with regular expressions for the product ID, and applies the `loggingMiddleware` to all routes using `r.Use`. This middleware will be executed for every incoming request before the actual handler.
func main() {
r := mux.NewRouter()
// Route with regular expression for product ID (only numbers)
r.HandleFunc("/products/{id:[0-9]+}", productHandler)
// Route for categories
r.HandleFunc("/categories/{category}", categoryHandler)
// Apply the logging middleware to all routes
r.Use(loggingMiddleware)
fmt.Println("Server listening on port 8000")
log.Fatal(http.ListenAndServe(":8000", r))
}
Complete Code
The complete, runnable code demonstrating more complex routing and middleware.
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
"log"
)
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Request received: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func productHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
productID := vars["id"]
fmt.Fprintf(w, "Viewing product with ID: %s\n", productID)
//Example optional query parameter
discount := r.URL.Query().Get("discount")
if discount != "" {
fmt.Fprintf(w, "Discount applied: %s\n", discount)
}
}
func categoryHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
categoryName := vars["category"]
fmt.Fprintf(w, "Viewing products in category: %s\n", categoryName)
}
func main() {
r := mux.NewRouter()
// Route with regular expression for product ID (only numbers)
r.HandleFunc("/products/{id:[0-9]+}", productHandler)
// Route for categories
r.HandleFunc("/categories/{category}", categoryHandler)
// Apply the logging middleware to all routes
r.Use(loggingMiddleware)
fmt.Println("Server listening on port 8000")
log.Fatal(http.ListenAndServe(":8000", r))
}
Concepts Behind the Snippet
This snippet builds upon the previous one by introducing middleware, which allows you to intercept and modify requests before they reach your handlers. It also shows how to use regular expressions to define more specific route patterns and how to handle optional parameters in URLs using query strings. The combination of flexible routing rules and middleware makes it possible to create powerful and adaptable web applications.
Real-Life Use Case Section
In a real-world e-commerce application, you might use regular expressions to validate product IDs, middleware to handle authentication, and query parameters to filter products based on different criteria (e.g., price range, availability).
Best Practices
Interview Tip
Understand the concept of middleware chaining. Middleware functions are executed in the order they are applied to the router. Be prepared to discuss different use cases for middleware (e.g., logging, authentication, authorization, rate limiting, request validation).
When to Use Them
Use middleware when you need to perform actions on every request or a group of requests, such as logging, authentication, or authorization. Use regular expressions in routes when you need to define more specific route patterns or validate route parameters.
Memory footprint
The memory footprint will be slightly higher compared to the basic example due to the added overhead of the middleware function. However, the difference is generally negligible unless your middleware performs very memory-intensive operations.
Alternatives
The alternatives are the same as the first example, but almost all of them support middleware as this feature is very useful. The `net/http` standard library have equivalents but it's more difficult to configure.
Pros
Cons
FAQ
-
Can I have multiple middleware functions?
Yes, you can chain multiple middleware functions using `r.Use`. They will be executed in the order they are added. -
How do I access the request context within middleware?
You can use the `context` package to store and retrieve values in the request context. This allows you to pass data between middleware functions and handlers. -
Can middleware modify the response?
Yes, middleware can modify the response before it is sent to the client. For example, you could add headers or compress the response body.