serve_json.go in Go
A JSON API with the standard mux: routes, middleware, graceful shutdown.
// Command serve_json runs a small JSON API over the standard library.
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"sync"
"time"
)
type tour struct {
Slug string `json:"slug"`
Title string `json:"title"`
Steps int `json:"steps"`
}
type store struct {
mu sync.RWMutex
tours map[string]tour
}
func (s *store) get(slug string) (tour, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
found, ok := s.tours[slug]
return found, ok
}
func writeJSON(w http.ResponseWriter, status int, body any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(body)
}
func routes(s *store) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter,
r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
})
mux.HandleFunc("GET /tours/{slug}", func(w http.ResponseWriter,
r *http.Request) {
found, ok := s.get(r.PathValue("slug"))
if !ok {
writeJSON(w, http.StatusNotFound,
map[string]string{"error": "no such tour"})
return
}
writeJSON(w, http.StatusOK, found)
})
return withLogging(mux)
}
func withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
started := time.Now()
next.ServeHTTP(w, r)
slog.Info("request", "method", r.Method, "path", r.URL.Path,
"took", time.Since(started).Round(time.Microsecond))
})
}
func main() {
data := &store{tours: map[string]tour{
"basics": {Slug: "basics", Title: "Language basics", Steps: 30},
}}
server := &http.Server{
Addr: "127.0.0.1:8099",
Handler: routes(data),
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
slog.Info("listening", "addr", server.Addr)
if err := server.ListenAndServe(); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
slog.Error("serve", "err", err)
}
}()
time.Sleep(50 * time.Millisecond) // stand-in for a signal
shutdownCtx, cancel := context.WithTimeout(context.Background(),
5*time.Second)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
fmt.Println("forced shutdown:", err)
}
slog.Info("stopped cleanly")
}
How it works
- The 1.22 mux handles the method and the path variable.
- Middleware wraps the whole mux, so every route is logged.
- Shutdown waits for in-flight requests, with its own deadline.
Keywords and builtins used here
anybooldeferfuncgoifintmapreturnstringstructtype
The run, in numbers
- Lines
- 97
- Characters to type
- 2140
- Tokens
- 543
- Three-star pace
- 115 tpm
At the three-star pace of 115 tokens a minute, this run takes about 283 seconds.
Step 1 of 1 in Encore, step 26 of 26 in Standard library & project layout.