From 1927c640472bed6410ed2a735b8b13150103385b Mon Sep 17 00:00:00 2001 From: Maxime Delporte Date: Sun, 19 Oct 2025 11:47:25 +0200 Subject: [PATCH] Creating an envelope struct type inside our helpers file to update our writeJSON data type method's parameter. Updating healthcheckHandler and showMovieHandler with this new type to update our responses. --- README.md | 11 ++++++++++- cmd/api/healthcheck.go | 4 ++-- cmd/api/helpers.go | 4 +++- cmd/api/movies.go | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8ba2b66..c6e4284 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,18 @@ A few other important things to mention : - Channels, functions and **complex** number types cannot be encoded. If you try to do so, you'll get a **json.UnsupportedTypeError** error at runtime. - Any pointer values will encode as *the value pointed to*. +### Enveloping responses + +The data of the endpoint /v1/movies/123 is nested under the key "movie", rather than being the top-level JSON object itself. +Enveloping response data like this isn't strictly necessary, and whether you choose to do so is partly a matter of style and taste. But there are a few tangible benefits : + +1. Including a key name (like "movie") at the top-level of the JSON helps make the response more self-documenting. For any humans who see the response out of context, it is a bit easier to understand what the data relates to. +2. It reduces the risk of errors on the client side, because it's harder to accidentally process one response thinking that it is something different. To get at the data, a client must explicitly reference it via the "movie" key. +3. If we always envelope the data returned by our API, then we mitigate a security vulnerability in older browsers which can arise if you return a JSON array as a response. + ### Performance json.MarshalIndent() takes 65% longer to run and uses around 30% more memory than json.Marshal(), as well as making two more heap allocations. Those figures will change depending on what you're encoding, but they're fairly indicative of the performance impact. For most applications this performance difference simply isn't something that you need to worry about. In real terms, we're talking about a few thousandths of a millisecond - and the improved readability of responses is probably worth this trade-off. -But if your API is operating in a very resource-constrained environment, or needs to manage extremely high levels of traffic, then this is worth being aware of and you may prefer to stick with using json.Marshal() instead. \ No newline at end of file +But if your API is operating in a very resource-constrained environment, or needs to manage extremely high levels of traffic, then this is worth being aware of, and you may prefer to stick with using json.Marshal() instead. \ No newline at end of file diff --git a/cmd/api/healthcheck.go b/cmd/api/healthcheck.go index 418ed8f..2c6d2a4 100644 --- a/cmd/api/healthcheck.go +++ b/cmd/api/healthcheck.go @@ -13,13 +13,13 @@ initialize it in main() */ func (app *application) healthcheckHandler(w http.ResponseWriter, r *http.Request) { // Create a map which holds the information that we want to send in the response. - data := map[string]string{ + env := envelope{ "status": "available", "environment": app.config.env, "version": version, } - err := app.writeJSON(w, http.StatusOK, data, nil) + err := app.writeJSON(w, http.StatusOK, env, nil) if err != nil { app.logger.Error(err.Error()) http.Error(w, "The server encountered a problem and could not process your request", http.StatusInternalServerError) diff --git a/cmd/api/helpers.go b/cmd/api/helpers.go index 3bd1458..ad7c5ae 100644 --- a/cmd/api/helpers.go +++ b/cmd/api/helpers.go @@ -43,12 +43,14 @@ func (app *application) readIDParam(r *http.Request) (int64, error) { return id, nil } +type envelope map[string]any + /* Define a writeJSON() helper for sending responses. This takes the destination http.ResponseWriter, the HTTP status code to send, the data to encode to JSON, and a header map containing any additional HTTP headers we want to include in the response. */ -func (app *application) writeJSON(w http.ResponseWriter, status int, data any, headers http.Header) error { +func (app *application) writeJSON(w http.ResponseWriter, status int, data envelope, headers http.Header) error { /* Use the json.MarshalIndent() function so that whitespace is added to the encoded JSON. Here we use no line prefix ("") and tab indents ("\t") for each element. diff --git a/cmd/api/movies.go b/cmd/api/movies.go index 6d6cafa..d197d05 100644 --- a/cmd/api/movies.go +++ b/cmd/api/movies.go @@ -36,7 +36,7 @@ func (app *application) showMovieHandler(w http.ResponseWriter, r *http.Request) } // Encode the struct to JSON and send it as the HTTP response. - err = app.writeJSON(w, http.StatusOK, movie, nil) + err = app.writeJSON(w, http.StatusOK, envelope{"movie": movie}, nil) if err != nil { app.logger.Error(err.Error()) http.Error(w, "The server encountered a problem and could not process your request", http.StatusInternalServerError)