Formatting and Enveloping Responses
So far in this book we’ve generally been making requests to our API using Firefox, which makes the JSON responses easy-to-read thanks to the ‘pretty printing’ provided by the built-in JSON viewer.
But if you try making some requests using curl, you’ll see that the actual JSON response data is all just on one line with no whitespace.
We can make these easier to read in terminals by using the json.MarshalIndent()
function to encode our response data, instead of the regular json.Marshal()
function. This automatically adds whitespace to the JSON output, putting each element on a separate line and prefixing each line with optional prefix and indent characters.
Let’s update our writeJSON()
helper to use this instead:
If you restart the API and try making the same requests from your terminal again, you will now receive some nicely-whitespaced JSON responses similar to these:
Relative performance
While using json.MarshalIndent()
is positive from a readability and user-experience point of view, it unfortunately doesn’t come for free. As well as the fact that the responses are now slightly larger in terms of total bytes, the extra work that Go does to add the whitespace has a notable impact on performance.
The following benchmarks help to demonstrate the relative performance of json.Marshal()
and json.MarshalIndent()
using the code in this gist.
In these benchmarks we can see that 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 in my experience 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.
Enveloping responses
Next let’s work on updating our responses so that the JSON data is always enveloped in a parent JSON object. Similar to this:
Notice how the movie data is nested under the key "movie"
here, 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:
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.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.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.
There are a couple of techniques that we could use to envelope our API responses, but we’re going to keep things simple and do it by creating a custom envelope
map with the underlying type map[string]any
.
I’ll demonstrate.
Let’s start by updating the cmd/api/helpers.go
file as follows:
Then we need to update our showMovieHandler
to create an instance of the envelope
map containing the movie data, and pass this onwards to our writeJSON()
helper instead of passing the movie data directly.
Like so:
We also need to update the code in our healthcheckHandler
so that it passes an envelope
type to the writeJSON()
helper too:
Alright, let’s try these changes out. Go ahead and restart the server, then use curl
to make some requests to the API endpoints again. You should now get responses formatted like the ones below.
Additional Information
Response structure
It’s important to emphasize that there’s no single right or wrong way to structure your JSON responses. There are some popular formats like JSON:API and jsend that you might like to follow or use for inspiration, but it’s certainly not necessary and most APIs don’t follow these formats.
But whatever you do, it is valuable to think about formatting upfront and to maintain a clear and consistent response structure across your different API endpoints — especially if they are being made available for public use.