Go, End to End
Building a Minimal HTTP Server
This is meant to be a practical walkthrough, not just syntax. Here's a real, complete, minimal HTTP server using only the standard library.
The basics: ServeMux and handlers
A handler is anything satisfying http.Handler (a ServeHTTP(w http.ResponseWriter, r *http.Request) method), but in practice you'll mostly write plain functions and register them with http.HandleFunc, which wraps them into handlers for you:
This example is meant to illustrate the pattern and won't let you curl it from this embedded panel (no real network access in the sandbox). Run it locally with http.ListenAndServe(":8080", nil) uncommented at the end of main, then visit http://localhost:8080/hello?name=World in a browser to see it respond for real.
A slightly more complete example
Real handlers usually need to inspect the method, read the URL path, and write a proper status code. http.ResponseWriter's WriteHeader sets the status; if you never call it, the first Write implicitly sends a 200.
package main
import (
"encoding/json"
"net/http"
)
type Health struct {
Status string `json:"status"`
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Health{Status: "ok"})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/health", healthHandler)
http.ListenAndServe(":8080", mux)
}Using an explicit http.NewServeMux() instead of the default global mux (the nil in the first example implicitly uses http.DefaultServeMux) is the better habit once a program has more than a couple routes: it avoids accidental route collisions between packages that both register against the global default.
The flag package for CLI configuration
Servers usually need a configurable port or a few other startup options. flag handles this without a third-party CLI framework:
var port = flag.Int("port", 8080, "port to listen on")
func main() {
flag.Parse()
addr := fmt.Sprintf(":%d", *port)
http.ListenAndServe(addr, nil)
}Run it as ./server -port 9090 and *port reflects the flag value (or the default, 8080, if the flag wasn't passed). flag is enough for straightforward tools; complex CLIs with subcommands often reach for a third-party library like cobra, but that's a scale decision, not a default.
Try it yourself: extend the health-check handler to also return the current time in the JSON response, then run it locally and hit it with curl.