Use Case: Building a REST API
Since Go 1.22, net/http’s built-in router handles method matching and path parameters, so a lot of small-to-medium APIs no longer need a third-party framework at all.
A minimal API
Section titled “A minimal API”package main
import ( "encoding/json" "log" "net/http")
type Task struct { ID string `json:"id"` Name string `json:"name"` Done bool `json:"done"`}
var tasks = map[string]Task{ "1": {ID: "1", Name: "Learn Go", Done: false},}
func main() { mux := http.NewServeMux()
mux.HandleFunc("GET /tasks", listTasks) mux.HandleFunc("GET /tasks/{id}", getTask) mux.HandleFunc("POST /tasks", createTask)
log.Println("listening on :8080") log.Fatal(http.ListenAndServe(":8080", mux))}
func listTasks(w http.ResponseWriter, r *http.Request) { list := make([]Task, 0, len(tasks)) for _, t := range tasks { list = append(list, t) } writeJSON(w, http.StatusOK, list)}
func getTask(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") task, ok := tasks[id] if !ok { writeJSON(w, http.StatusNotFound, map[string]string{"error": "task not found"}) return } writeJSON(w, http.StatusOK, task)}
func createTask(w http.ResponseWriter, r *http.Request) { var t Task if err := json.NewDecoder(r.Body).Decode(&t); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"}) return } tasks[t.ID] = t writeJSON(w, http.StatusCreated, t)}
func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(v)}Run it with go run main.go, then:
curl localhost:8080/taskscurl -X POST localhost:8080/tasks -d '{"id":"2","name":"Ship it"}'Where this scales from here
Section titled “Where this scales from here”- Persistence: swap the
map[string]Taskfordatabase/sql(withpgxorsqlite3) behind a smallTaskStoreinterface — see Interfaces. - Validation & structured errors: return a consistent error shape (
{"error": {"code": "...", "message": "..."}}) instead of ad-hoc strings. - Middleware: wrap
muxwith logging, recovery, and auth middleware, chained as shown in Functions. - Graceful shutdown: listen for
SIGINT/SIGTERMand callserver.Shutdown(ctx)so in-flight requests finish cleanly — this is where Context pays off directly.