Introduction
Go ships with a production-grade HTTP server in its standard library. Unlike most languages, you rarely need a third-party framework to build robust web services in Go.
Key Concepts
- http.Handler: An interface requiring a single method
ServeHTTP(w, r)— the foundation of all request handling in Go. - http.HandleFunc: A convenience function that registers a plain function as a handler on the default ServeMux.
- http.ResponseWriter: The interface used to construct and send an HTTP response back to the client.
- http.ListenAndServe: Starts an HTTP server on a given address, blocking until an error occurs.
Real World Context
Most Go microservices in production use the standard library directly. Understanding net/http is essential because even frameworks like Gin and Echo build on top of it. Knowing the primitives lets you debug issues, write middleware, and avoid unnecessary dependencies.
Deep Dive
The simplest HTTP server in Go uses http.HandleFunc to register a handler function and http.ListenAndServe to start the server.
gofunc main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello World")) }) log.Fatal(http.ListenAndServe(":8080", nil)) }
Passing nil to ListenAndServe uses the default ServeMux. Every incoming request is dispatched to the matching handler.
For more control, implement the http.Handler interface directly. Any type with a ServeHTTP(w, r) method satisfies the interface.
gotype myHandler struct{} func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello from handler")) } func main() { http.Handle("/", &myHandler{}) log.Fatal(http.ListenAndServe(":8080", nil)) }
The http.HandlerFunc type adapter lets you convert any function with the right signature into an http.Handler, bridging the gap between functions and interfaces.
Common Pitfalls
- Forgetting to call
log.FatalonListenAndServe— If the server fails to start (e.g., port in use), the error is silently discarded withoutlog.Fatal. - Writing after
WriteHeader— Callingw.Write()implicitly sends a 200 status. If you callw.WriteHeader(404)afterw.Write(), the status code is ignored.
Best Practices
- Use
http.NewServeMux()instead of the default mux — The default mux is a global variable; creating your own avoids routing collisions in tests and large applications. - Always handle the
ListenAndServeerror — Wrap it inlog.Fatalor a structured logger to catch bind failures immediately.
Summary
- Go's
net/httppackage provides a complete HTTP server without external dependencies. - Handlers implement
ServeHTTP(w, r)or usehttp.HandleFuncfor convenience. http.ListenAndServestarts the server and blocks until failure.
Code Examples
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
})
log.Fatal(http.ListenAndServe(":8080", nil))
}