One Handler per Route
Routing decides which handler answers which request. Go's built-in router is http.ServeMux: you register patterns, it dispatches.
Patterns can include the method and path parameters in braces: "GET /users/{id}" matches GET /users/7 and hands you the 7 through r.PathValue("id"). Frameworks add conveniences on top, but this covers the everyday needs out of the box.
Routes and Path Values
Two routes: the list at /users and one user at /users/{id}. The braces part is the path parameter — the piece of the URL that is data.
A route with a path parameter
The same mux sends /users to one handler and /users/7 to another, and r.PathValue("id") extracts the 7 without any string cutting.
Output: Go program and captured output
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /users", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "all users: Ana, Ben, Cleo")
})
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "one user, id =", r.PathValue("id"))
})
server := httptest.NewServer(mux)
defer server.Close()
for _, path := range []string{"/users", "/users/7"} {
resp, _ := http.Get(server.URL + path)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Print("GET ", path, " -> ", string(body))
}
}
Query Parameters
The part after ? holds query parameters — options for the request, like a search text or a limit: /search?q=go&limit=2. They come from r.URL.Query(), always as strings, with Get returning "" for missing ones.
The rule of thumb: the path says which resource, the query says how — filters, sorting, paging.
Reading the query string
The handler reads q and limit from the query. The second request skips limit, so the handler falls back to a default — missing parameters are normal, plan for them.
Output: Go program and captured output
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /search", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("q")
limit := r.URL.Query().Get("limit")
if limit == "" {
limit = "10"
}
fmt.Fprintln(w, "searching for", q, "with limit", limit)
})
server := httptest.NewServer(mux)
defer server.Close()
for _, path := range []string{"/search?q=go&limit=2", "/search?q=json"} {
resp, _ := http.Get(server.URL + path)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Print("GET ", path, " -> ", string(body))
}
}