go 57 lines · 9 steps

Building reusable JSON helpers in Go HTTP handlers

A small set of helpers centralize JSON encoding and error responses so handlers stay focused on business logic.

Explained by highlit
1package api
2 
3import (
4 "encoding/json"
5 "log"
6 "net/http"
7)
8 
9type ErrorBody struct {
10 Error string `json:"error"`
11 Details string `json:"details,omitempty"`
12}
13 
14func WriteJSON(w http.ResponseWriter, status int, payload any) {
15 buf, err := json.Marshal(payload)
16 if err != nil {
17 log.Printf("api: failed to marshal response: %v", err)
18 w.Header().Set("Content-Type", "application/json; charset=utf-8")
19 w.WriteHeader(http.StatusInternalServerError)
20 w.Write([]byte(`{"error":"internal server error"}`))
21 return
22 }
23 
24 w.Header().Set("Content-Type", "application/json; charset=utf-8")
25 w.Header().Set("X-Content-Type-Options", "nosniff")
26 w.Header().Set("Cache-Control", "no-store")
27 w.WriteHeader(status)
28 w.Write(buf)
29}
30 
31func WriteError(w http.ResponseWriter, status int, msg, details string) {
32 WriteJSON(w, status, ErrorBody{Error: msg, Details: details})
33}
34 
35func GetUser(store UserStore) http.HandlerFunc {
36 return func(w http.ResponseWriter, r *http.Request) {
37 id := r.PathValue("id")
38 if id == "" {
39 WriteError(w, http.StatusBadRequest, "missing user id", "")
40 return
41 }
42 
43 user, err := store.FindByID(r.Context(), id)
44 switch {
45 case err == ErrNotFound:
46 WriteError(w, http.StatusNotFound, "user not found", id)
47 return
48 case err != nil:
49 log.Printf("api: lookup failed for %s: %v", id, err)
50 WriteError(w, http.StatusInternalServerError, "failed to load user", "")
51 return
52 }
53 
54 w.Header().Set("ETag", user.ETag())
55 WriteJSON(w, http.StatusOK, user)
56 }
57}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Centralizing serialization in one helper guarantees consistent headers and status codes across every handler.
  2. 2Returning a closure from a constructor function lets handlers capture dependencies like a data store cleanly.
  3. 3Distinguishing not-found from unexpected errors lets you map each to the correct HTTP status.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building reusable JSON helpers in Go HTTP handlers — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code