The net/http package implements a full HTTP/1.1 and HTTP/2 client and server without any third-party dependency. A handler is any value satisfying the http.Handler interface (one method: ServeHTTP), and http.HandleFunc lets an ordinary function serve as a handler directly. http.ListenAndServe starts the server and blocks, handling each incoming request concurrently in its own goroutine automatically.
A minimal server
A handler function takes an http.ResponseWriter to write the response and an *http.Request describing the incoming request.
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
}
func main() {
http.HandleFunc("/hello", hello)
log.Fatal(http.ListenAndServe(":8080", nil)) // blocks; visit localhost:8080/hello?name=Go
}Routing with net/http's ServeMux
Since Go 1.22 (2024), the standard library's router (http.ServeMux) natively supports method matching and path wildcards — patterns that previously required a third-party router package.
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUser) // method + path wildcard (Go 1.22+)
mux.HandleFunc("POST /users", createUser)
log.Fatal(http.ListenAndServe(":8080", mux))
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // extracts {id} from the matched path
fmt.Fprintf(w, "user %s", id)
}Every request runs in its own goroutine
The server automatically handles each incoming request in a new goroutine, so a slow request doesn't block others — this is Go's concurrency model applied transparently at the framework level. It also means handler code that touches shared state (a cache, a counter) needs the same synchronization (sync.Mutex, channels) as any other concurrent Go code.