net/http is production-grade; many Go services use nothing else for HTTP.
net/http contains both a production-ready server and a client.
Server. A handler is anything implementing ServeHTTP(w http.ResponseWriter, r *http.Request); usually you register functions on a *http.ServeMux. Since Go 1.22 patterns include the method and wildcards:
mux.HandleFunc("GET /items/{id}", getItem)— read the wildcard withr.PathValue("id")."POST /items"matches only POST; a mismatched method gets a 405 automatically."/static/"with a trailing slash matches the whole subtree;{path...}captures the rest.
In a handler, set headers with w.Header().Set(...) before w.WriteHeader(status) or the first Write. r.Context() is cancelled when the client disconnects. Each request runs in its own goroutine, so shared state needs synchronisation.
Middleware is just a function func(http.Handler) http.Handler wrapping another handler — for logging, auth or recovering panics.
Use an explicit http.Server with timeouts rather than http.ListenAndServe, which has none; slow clients could otherwise hold connections forever. For graceful shutdown, call srv.Shutdown(ctx) on SIGTERM.
Client. http.Get uses http.DefaultClient, which has no timeout. Create a client with &http.Client{Timeout: 10 * time.Second} and reuse it — it pools connections. Build requests with http.NewRequestWithContext, always defer resp.Body.Close(), and check resp.StatusCode: a 404 or 500 is not an error.
net/http/httptest lets you test handlers without a network (httptest.NewRecorder) or spin up a real test server.
Example
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
func getItem(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"id": id, "name": "widget"})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /items/{id}", getItem)
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
log.Fatal(srv.ListenAndServe()) // curl localhost:8080/items/42
}Remember
- Go 1.22+ ServeMux patterns:
"GET /items/{id}"+r.PathValue - Set server and client timeouts; the defaults have none
- Always close resp.Body and check StatusCode yourself
Watch out for
- Calling w.Header().Set after w.WriteHeader or Write — the header is already sent and the change is ignored.