# Routing in Go with Gorilla Mux

When building web applications in Go, routing is one of the fundamental
components you'll need to master.

While Go's standard library provides basic routing capabilities, most production
applications require more sophisticated routing features.

This is where [Gorilla Mux](https://github.com/gorilla/mux) comes in. It's a powerful, flexible router that
extends Go's native capabilities while maintaining its simplicity and
performance.

[ad-logs]

## The limitations of the Go standard library router

Go's standard library includes the `net/http` package, which provides basic
routing capabilities. With the standard library, you can create simple routes
like this:

```go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", homeHandler)
	http.HandleFunc("/about", aboutHandler)
	http.ListenAndServe(":8080", nil)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome to the home page!")
}

func aboutHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "About us page")
}
```

To run this example, save it as `main.go`, then execute:

```command
go run main.go
```

Then visit `http://localhost:8080/` in your browser to see the home page
content.

![1.png](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/1d73715e-462d-477f-0213-dc92c63c5600/orig =1432x405)

While the standard library router works for simple applications, it lacks
features like path variables, pattern matching, HTTP method filtering, and
convenient middleware support.

Gorilla Mux fills these gaps while maintaining compatibility with Go's standard
interfaces.

## Getting started with Gorilla Mux

Let's start by setting up a basic project using Gorilla Mux. First, create a new
project directory and initialize a Go module:

```command
mkdir gorilla-demo && cd gorilla-demo
```

```command
go mod init github.com/<yourusername>/gorilla-demo
```

Next, install the Gorilla Mux package:

```command
go get -u github.com/gorilla/mux
```

Now, create a simple application with Gorilla Mux. Create a file named `main.go`
with the following content:

```go
[label main.go]
package main

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

	"github.com/gorilla/mux"
)

func homeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome to our website!")
}

func aboutHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "About our company")
}

func main() {
	// Create a new router
	r := mux.NewRouter()

	// Register routes
	r.HandleFunc("/", homeHandler).Methods("GET")
	r.HandleFunc("/about", aboutHandler).Methods("GET")

	fmt.Println("Server starting on port 8080...")
	log.Fatal(http.ListenAndServe(":8080", r))
}
```

Run the application:

```command
go run main.go
```

Visit `http://localhost:8080/` and `http://localhost:8080/about` to see the
pages. Notice that we're now explicitly specifying that these routes should only
match GET requests with `.Methods("GET")`.

This is one of Gorilla Mux's key features: the ability to filter routes by HTTP
method.

## Basic routing concepts

Gorilla Mux extends Go's routing capabilities with URL patterns, path variables,
and HTTP method filtering. Let's explore these concepts with practical examples.

### URL patterns and path variables

One of Gorilla Mux's most powerful features is the ability to capture variables
from URL paths:

```go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

// Product represents a product in our system
type Product struct {
	ID    string  `json:"id"`
	Name  string  `json:"name"`
	Price float64 `json:"price"`
}

func getProductHandler(w http.ResponseWriter, r *http.Request) {
	// Extract the id from the URL path
	vars := mux.Vars(r)
	productID := vars["id"]

	// In a real app, you'd fetch from database
	product := Product{
		ID:    productID,
		Name:  "Sample Product",
		Price: 29.99,
	}

	// Send JSON response
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(product)
}

func main() {
	r := mux.NewRouter()

	// Route with a path variable
	r.HandleFunc("/products/{id}", getProductHandler).Methods("GET")

	fmt.Println("Server starting on port 8080...")
	log.Fatal(http.ListenAndServe(":8080", r))
}
```

Run this example and visit `http://localhost:8080/products/123` in your browser
or use curl:

```command
curl http://localhost:8080/products/123
```

```text
[output]
{"id":"123","name":"Sample Product","price":29.99}
```

You'll receive a JSON response with the product ID "123". The path variable
`{id}` captures any value in that segment of the URL, and `mux.Vars(r)`
retrieves these variables as a map.

### HTTP method-specific routes

Gorilla Mux lets you register different handlers for the same path based on the
HTTP method:

```go
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func getProductsHandler(w http.ResponseWriter, r *http.Request) {
	products := []map[string]interface{}{
		{"id": "1", "name": "Laptop", "price": 999.99},
		{"id": "2", "name": "Smartphone", "price": 499.99},
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(products)
}

func createProductHandler(w http.ResponseWriter, r *http.Request) {
	// Read request body
	_, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Error reading request body", http.StatusBadRequest)
		return
	}

	// In a real app, you'd parse and validate the JSON

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	w.Write([]byte(`{"message": "Product created successfully"}`))
}

func main() {
	r := mux.NewRouter()

	// Same path, different handlers based on HTTP method
	r.HandleFunc("/products", getProductsHandler).Methods("GET")
	r.HandleFunc("/products", createProductHandler).Methods("POST")

	fmt.Println("Server starting on port 8080...")
	log.Fatal(http.ListenAndServe(":8080", r))
}
```

Test these routes with `curl`:

```command
curl http://localhost:8080/products
```

```json
[output]
[{"id":"1","name":"Laptop","price":999.99},{"id":"2","name":"Smartphone","price":499.99}]
```

```command
curl -X POST -H "Content-Type: application/json" -d '{"name":"New Product","price":19.99}' http://localhost:8080/products
```

```json
[output]
{"message": "Product created successfully"}
```

This pattern is particularly useful for RESTful APIs, where different HTTP
methods represent different operations on the same resource.

### Pattern matching with regular expressions

You can use regular expressions in path variables to constrain what they match:

```go
package main

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

	"github.com/gorilla/mux"
)

func articleHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	fmt.Fprintf(w, "Article: %s\nDate: %s/%s\nCategory: %s",
		vars["slug"], vars["month"], vars["year"], vars["category"])
}

func userHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	fmt.Fprintf(w, "User profile: %s", vars["username"])
}

func main() {
	r := mux.NewRouter()

	// Year must be exactly 4 digits, month exactly 2 digits
	r.HandleFunc("/articles/{category}/{year:[0-9]{4}}/{month:[0-9]{2}}/{slug}",
		articleHandler).Methods("GET")

	// Username must be lowercase letters and numbers only
	r.HandleFunc("/users/{username:[a-z0-9]+}", userHandler).Methods("GET")

	fmt.Println("Server starting on port 8080...")
	log.Fatal(http.ListenAndServe(":8080", r))
}
```

Test these routes with your browser or curl:

```command
curl http://localhost:8080/articles/technology/2023/05/introduction-to-go # Valid article URL
```

```text
[output]
Article: introduction-to-go
Date: 05/2023
Category: technology
```

```command
curl http://localhost:8080/users/john123 # Valid username
```

```text
[output]
User profile: john123
```

```command
curl http://localhost:8080/users/John123 # Invalid username (contains uppercase)
```

```text
[output]
404 page not found
```

The first and second requests will succeed, but the third will result in a 404
Not Found response because the username doesn't match the specified pattern.

### Subrouters for route grouping

As your application grows, organizing routes becomes essential. Gorilla Mux
provides subrouters for this purpose:

```go
[label subrouters.go]
package main

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

    "github.com/gorilla/mux"
)

func apiGetUsersHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "API: Get users")
}

func apiCreateUserHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "API: Create user")
}

func adminDashboardHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Admin: Dashboard")
}

func adminUsersHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Admin: Users")
}

func main() {
    r := mux.NewRouter()

    // API subrouter
    api := r.PathPrefix("/api/v1").Subrouter()
    api.HandleFunc("/users", apiGetUsersHandler).Methods("GET")
    api.HandleFunc("/users", apiCreateUserHandler).Methods("POST")

    // Admin subrouter
    admin := r.PathPrefix("/admin").Subrouter()
    admin.HandleFunc("/dashboard", adminDashboardHandler).Methods("GET")
    admin.HandleFunc("/users", adminUsersHandler).Methods("GET")

    fmt.Println("Server starting on port 8080...")
    log.Fatal(http.ListenAndServe(":8080", r))
}
```

Test these routes:

```bash
curl http://localhost:8080/api/v1/users
curl -X POST http://localhost:8080/api/v1/users
curl http://localhost:8080/admin/dashboard
curl http://localhost:8080/admin/users
```

Subrouters create clean URL structures and help organize your code by grouping
related routes. The paths registered on a subrouter are relative to its prefix,
making the code more readable and maintainable.

## Middleware integration

Middleware functions in Gorilla Mux run before or after your normal route
handlers, perfect for cross-cutting concerns:

```go
[label middleware.go]
package main

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

    "github.com/gorilla/mux"
)

// LoggingMiddleware logs request details
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 after the request is processed
        log.Printf("%s %s took %s", r.Method, r.RequestURI, time.Since(start))
    })
}

// AuthMiddleware checks for a valid token
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")

        if token != "secret-token" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }

        // Token is valid, call the next handler
        next.ServeHTTP(w, r)
    })
}

func publicHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Public resource")
}

func privateHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Private resource")
}

func main() {
    r := mux.NewRouter()

    // Apply logging middleware to all routes
    r.Use(loggingMiddleware)

    // Public routes
    r.HandleFunc("/public", publicHandler).Methods("GET")

    // Private routes with auth middleware
    private := r.PathPrefix("/private").Subrouter()
    private.Use(authMiddleware)
    private.HandleFunc("", privateHandler).Methods("GET")

    fmt.Println("Server starting on port 8080...")
    log.Fatal(http.ListenAndServe(":8080", r))
}
```

Test these routes:

```command
# Public route (no auth required)
curl http://localhost:8080/public

# Private route without auth token
curl http://localhost:8080/private

# Private route with auth token
curl -H "Authorization: secret-token" http://localhost:8080/private
```

The logging middleware runs for all requests, while the auth middleware only
applies to the private subrouter. The middleware executes in the order it's
registered, so requests to `/private` will be logged even if authentication
fails.

## Request validation and processing

Gorilla Mux simplifies handling different types of request data. Let's explore
some common patterns. Here's a complete example of processing a JSON request:

```go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

type User struct {
	Username string `json:"username"`
	Email    string `json:"email"`
	Password string `json:"password,omitempty"`
}

func createUserHandler(w http.ResponseWriter, r *http.Request) {
	var user User

	// Decode JSON request
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&user); err != nil {
		http.Error(w, "Invalid request format", http.StatusBadRequest)
		return
	}

	// Validate required fields
	if user.Username == "" || user.Email == "" || user.Password == "" {
		http.Error(w, "All fields are required", http.StatusBadRequest)
		return
	}

	// In a real app, you'd save the user to a database
	// For this example, we'll just return a success message

	// Create response
	response := map[string]string{
		"id":      "123", // In a real app, this would be the generated ID
		"message": "User created successfully",
	}

	// Send JSON response
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated) // 201 Created
	json.NewEncoder(w).Encode(response)
}

func main() {
	r := mux.NewRouter()

	r.HandleFunc("/users", createUserHandler).Methods("POST")

	fmt.Println("Server starting on port 8080...")
	log.Fatal(http.ListenAndServe(":8080", r))
}
```

Test this route:

```command
curl -X POST -H "Content-Type: application/json" -d '{"username":"john_doe","email":"john@example.com","password":"secret123"}' http://localhost:8080/users
```

```json
[output]
{"id":"123","message":"User created successfully"}
```

The handler decodes the JSON request body into a Go struct, validates the
required fields, and returns a JSON response with the appropriate status code.
In a real application, you'd also add more validation and error handling.

## Error handling

Consistent error handling improves API usability and maintainability. Here's how
to implement custom error handling with Gorilla Mux:

```go
[label error_handling.go]
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

// ErrorResponse represents a standardized error response
type ErrorResponse struct {
    Status  int    `json:"status"`
    Message string `json:"message"`
    Error   string `json:"error,omitempty"`
}

// SendError sends a standardized error response
func sendError(w http.ResponseWriter, status int, message string, err error) {
    response := ErrorResponse{
        Status:  status,
        Message: message,
    }

    if err != nil {
        response.Error = err.Error()
    }

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(response)
}

// NotFoundHandler handles 404 errors
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
    sendError(w, http.StatusNotFound, "The requested resource was not found", nil)
}

// RecoveryMiddleware recovers from panics
func recoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                // Log the error
                log.Printf("Panic recovered: %v", err)

                // Return error to client
                sendError(w, http.StatusInternalServerError,
                          "An unexpected error occurred", nil)
            }
        }()

        next.ServeHTTP(w, r)
    })
}

func getUserHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    userID := vars["id"]

    // Simulate a "not found" error for user with ID "0"
    if userID == "0" {
        sendError(w, http.StatusNotFound, "User not found", nil)
        return
    }

    // Simulate a panic for user with ID "panic"
    if userID == "panic" {
        panic("Simulated panic")
    }

    // Normal response
    user := map[string]string{
        "id":       userID,
        "username": "john_doe",
        "email":    "john@example.com",
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func main() {
    r := mux.NewRouter()

    // Apply recovery middleware
    r.Use(recoveryMiddleware)

    // Register routes
    r.HandleFunc("/users/{id}", getUserHandler).Methods("GET")

    // Set custom 404 handler
    r.NotFoundHandler = http.HandlerFunc(notFoundHandler)

    fmt.Println("Server starting on port 8080...")
    log.Fatal(http.ListenAndServe(":8080", r))
}
```

Test these error handling scenarios:

```command
curl http://localhost:8080/users/123 # Normal request
```

```command
curl http://localhost:8080/users/0 # Not found user
```

```command
curl http://localhost:8080/users/panic # Trigger panic (which will be recovered)
```

```command
curl http://localhost:8080/nonexistent # Not found route
```

This example demonstrates three error handling patterns:

1. Function-level error handling in `getUserHandler` for application-specific
   errors.
2. A custom 404 handler for routes that don't match any registered pattern.
3. A recovery middleware that catches panics and prevents the server from
   crashing.

These patterns provide consistent error responses across your application,
improving the developer experience for API consumers.

## Final thoughts

Gorilla Mux enhances Go's routing capabilities without straying from the
language's philosophy of simplicity and practicality.

Its path variables, regex patterns, subrouters, and middleware support provide
the tools needed for building sophisticated web applications while maintaining
compatibility with Go's standard library.

The router's approach to organization and error handling promotes maintainable,
robust code that scales with your application's complexity. Whether you're
building a simple API or a complex web service, Gorilla Mux offers a solid
foundation that grows with your needs.

As you work with Gorilla Mux, remember that it follows Go's convention of being
explicit rather than magical. This might require a bit more code compared to
some other web frameworks, but it results in more maintainable and
understandable applications in the long run.
