Routing HTTP Requests
Learn how to route HTTP requests using both the enhanced standard library and third-party libraries in Go.
Routing HTTP requests efficiently is crucial when building web applications. Go's standard net/http
package now includes enhanced routing capabilities with method-specific handlers and path wildcards, while third-party libraries like Gorilla Mux provide additional advanced features.
Standard Library Routing
The standard net/http
package now supports method-specific handlers and path wildcards for more powerful routing:
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Creating a new user")
})
// Path wildcards with method specification.
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
userID := r.PathValue("id")
fmt.Fprintf(w, "User ID: %s\n", userID)
})
// Wildcard segments can capture multiple path elements.
mux.HandleFunc("GET /files/{path...}", func(w http.ResponseWriter, r *http.Request) {
filePath := r.PathValue("path")
fmt.Fprintf(w, "File path: %s\n", filePath)
})
http.ListenAndServe(":8080", mux)
}
Advanced Standard Library Patterns
Use more sophisticated routing patterns with the enhanced standard library:
package main
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
mux := http.NewServeMux()
// RESTful API routes with method-specific handlers.
mux.HandleFunc("GET /api/users", listUsers)
mux.HandleFunc("POST /api/users", createUser)
mux.HandleFunc("GET /api/users/{id}", getUser)
mux.HandleFunc("PUT /api/users/{id}", updateUser)
mux.HandleFunc("DELETE /api/users/{id}", deleteUser)
// Nested resource routing.
mux.HandleFunc("GET /api/users/{userID}/posts/{postID}", getUserPost)
// File serving with wildcard.
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
http.ListenAndServe(":8080", mux)
}
func listUsers(w http.ResponseWriter, r *http.Request) {
users := []User{{1, "Alice"}, {2, "Bob"}}
json.NewEncoder(w).Encode(users)
}
func createUser(w http.ResponseWriter, r *http.Request) {
var user User
json.NewDecoder(r.Body).Decode(&user)
user.ID = 3 // Simulate ID assignment
json.NewEncoder(w).Encode(user)
}
func getUser(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
user := User{ID: id, Name: "Example User"}
json.NewEncoder(w).Encode(user)
}
func updateUser(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
fmt.Fprintf(w, "Updating user %s\n", idStr)
}
func deleteUser(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
fmt.Fprintf(w, "Deleting user %s\n", idStr)
}
func getUserPost(w http.ResponseWriter, r *http.Request) {
userID := r.PathValue("userID")
postID := r.PathValue("postID")
fmt.Fprintf(w, "User %s, Post %s\n", userID, postID)
}
Basic Routing with Gorilla Mux
For more advanced routing features, Gorilla Mux remains a popular choice:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
// Define a simple route.
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Welcome to the home page!")
})
// Start the server.
http.ListenAndServe(":8080", r)
}
Handling URL Parameters
Gorilla Mux makes it easy to capture URL parameters and use them in handler functions.
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
// Define a route with a URL parameter.
r.HandleFunc("/user/{name}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
fmt.Fprintf(w, "Hello, %s!\n", name)
})
// Start the server.
http.ListenAndServe(":8080", r)
}
Using Middleware with Gorilla Mux
Middleware functions can be used to execute common logic for multiple routes.
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
// Logger middleware.
func logger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Request URL:", r.URL)
next.ServeHTTP(w, r)
})
}
func main() {
r := mux.NewRouter()
// Apply middleware to all routes.
r.Use(logger)
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Welcome to the home page with logging!")
})
// Start the server.
http.ListenAndServe(":8080", r)
}
Subrouters for Modular Design
Subrouters can help manage routes by grouping them logically, which is useful for complex applications.
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
// Create a subrouter.
api := r.PathPrefix("/api").Subrouter()
api.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "API is running")
})
// Start the server.
http.ListenAndServe(":8080", r)
}
Best Practices
- Prefer the enhanced standard library routing for most use cases; it's now powerful enough for many applications.
- Use method-specific handlers (
GET /path
,POST /path
) for cleaner, more explicit routing. - Leverage path wildcards (
{id}
) and catch-all patterns ({path...}
) for flexible routing. - Employ middleware for logging, authentication, and other cross-cutting concerns.
- Use URL parameters wisely to keep endpoints clean and intuitive.
- Organize routes using subrouters to separate different parts of your API.
Common Pitfalls
- Forgetting to start the server with
http.ListenAndServe
. - Not closing response bodies in middleware or handlers if necessary.
- Failing to handle errors when retrieving URL params, leading to runtime panics.
- Misconfiguring paths in complex routes, which can lead to unexpected behavior.
Performance Tips
- Define routes specifically to avoid unnecessary regular expression parsing.
- Minimize middleware layers in performance-critical paths to reduce request processing time.
- Prioritize routes from most specific to least specific to optimize matching speed.
- Reuse handlers where possible to avoid performance costs associated with repeated object creation.