Lesson 19 · 19 of 20
HTTP handlers and httptest · Go
Write an HTTP handler in Go with net/http that answers in JSON, and test it with httptest, no network or server. This is how the Web (Go) target works.

A web server in Go is, above all, a handler: a function with the signature func(w http.ResponseWriter, r *http.Request). r carries the request (method, path, parameters) and the response is written to w. In DMarlon Studio, Go is the server of the Web (Go) target: the app's pages ask handlers like this one for their data.
priceHandler reads the parameter with r.URL.Query().Get("sku") and looks it up in prices. The switch picks the answer: if the method is not GET, `http.Error` writes status 405 and a message; if the product does not exist, 404. When no status is set, the response goes out as 200.
In the good case, the headers are set first with w.Header().Set("Content-Type", "application/json") (once the body is written they can no longer change), and then json.NewEncoder(w).Encode(...) writes the JSON straight into the response. Since it is a map, the keys come out in alphabetical order: price before sku.
`http.HandlerFunc(priceHandler)` turns the function into an http.Handler, the interface all of net/http works with. HandlerFunc is a function type with a ServeHTTP method that calls the function itself: interfaces and function values working together.
Testing it needs no network. `httptest.NewRequest` builds a request and `httptest.NewRecorder` is a ResponseWriter that keeps whatever is written: rec.Code, the headers and rec.Body. To serve it for real, http.ListenAndServe(":8080", handler) would be enough.
Code · main.go
package mainimport ( "encoding/json" "fmt" "net/http" "net/http/httptest")func priceHandler(w http.ResponseWriter, r *http.Request) { // GET /api/price?sku=... prices := map[string]float64{"KB-01": 49.9, "MS-02": 19.5} sku := r.URL.Query().Get("sku") price, ok := prices[sku] switch { case r.Method != http.MethodGet: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) case !ok: http.Error(w, "unknown sku", http.StatusNotFound) default: w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]any{"sku": sku, "price": price}) }}func main() { handler := http.HandlerFunc(priceHandler) // the adapter that makes it an http.Handler requests := [][2]string{{"GET", "/api/price?sku=KB-01"}, {"GET", "/api/price?sku=ZZ-09"}, {"POST", "/api/price?sku=KB-01"}, {"GET", "/api/price?sku=MS-02"}} for _, req := range requests { rec := httptest.NewRecorder() // records status, headers and body: no network handler.ServeHTTP(rec, httptest.NewRequest(req[0], req[1], nil)) fmt.Printf("%-4s %-21s -> %d %s", req[0], req[1], rec.Code, rec.Body) }}Output · go run main.go
GET /api/price?sku=KB-01 -> 200 {"price":49.9,"sku":"KB-01"}
GET /api/price?sku=ZZ-09 -> 404 unknown sku
POST /api/price?sku=KB-01 -> 405 method not allowed
GET /api/price?sku=MS-02 -> 200 {"price":19.5,"sku":"MS-02"}Key points
- A handler takes an
http.ResponseWriterand an*http.Request. http.Errorwrites an error status and a message.- Set the headers before writing the body.
http.HandlerFuncturns a function into anhttp.Handler.httptesttests handlers with no network or server.
Exercise
Make a request with no sku (/api/price) answer 400 with the text missing sku using http.StatusBadRequest, and add it to requests to check it.