Go cookbook · Beginner

Route HTTP requests with the standard library ServeMux

You want method-based routes and path parameters without adding a router dependency.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Since Go 1.22, http.ServeMux patterns include the method and wildcards (GET /items/{id}); read them with r.PathValue. Unmatched methods get 405 automatically. Configure server timeouts explicitly.

Solution

package main

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"time"
)

type Item struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /items", listItems)
	mux.HandleFunc("GET /items/{id}", getItem)
	mux.HandleFunc("POST /items", createItem)
	mux.HandleFunc("GET /files/{path...}", serveFile) // wildcard for the rest of the path

	srv := &http.Server{
		Addr:              ":8080",
		Handler:           mux,
		ReadHeaderTimeout: 5 * time.Second,
		ReadTimeout:       15 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       60 * time.Second,
	}
	log.Fatal(srv.ListenAndServe())
}

func getItem(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(r.PathValue("id"))
	if err != nil {
		http.Error(w, "id must be a number", http.StatusBadRequest)
		return
	}
	writeJSON(w, http.StatusOK, Item{ID: id, Name: "Teapot"})
}

func listItems(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, []Item{{ID: 1, Name: "Mug"}})
}

func createItem(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusCreated, Item{ID: 2, Name: "Kettle"})
}

func serveFile(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("file: " + r.PathValue("path")))
}

func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(v)
}

Pitfalls

  • Using http.ListenAndServe with the default server, which has no timeouts (slowloris).
  • Registering overlapping patterns that panic at startup; patterns must not be ambiguous.
  • Writing headers after WriteHeader or the body; they're ignored.

Variations

  • Group routes with prefixes by mounting sub-muxes: mux.Handle("/api/", http.StripPrefix("/api", apiMux)).
  • Third-party routers (chi) add route groups and middleware stacks on the same http.Handler interface.

Further reading: net/http ServeMux

Reference: https://pkg.go.dev/net/http#ServeMux

More Go recipes

Was this page helpful?