# Endpoints | Method | URL Pattern | Handler | Action | |--------|-----------------|---------------------|--------------------------------------| | GET | /v1/healthcheck | healthCheckHandler | Show application information | | GET | /v1/movies | listMoviesHandler | Show the details of all movies | | POST | /v1/movies | createMoviesHandler | Create a new movie | | GET | /v1/movies/:id | showMovieHandler | Show the details of a specific movie | | PUT | /v1/movies/:id | editMovieHandler | Edit the details of a specific movie | | DELETE | /v1/movies/:id | deleteMovieHandler | Delete a specific movie | # Installation ## Launch API `go run ./cmd/api` If you want, you can also verify that the command-line flags are working correctly by specifying alternative **port** and **env** values when starting the application. When you do this, you should see the contents of the log message change accordingly. For example : `go run ./cmd/api -port=3030 -env=production` **time=2025-10-10T11:08:00.000+02:00 level=INFO msg= "starting server" addr=:3030 env=production** ## Test endpoints `curl -i localhost:4000/v1/healthcheck` The *-i* flag in the command above instructs curl to display the HTTP response headers as well as the response body. ### Result HTTP/1.1 200 OK Date: Mon, 05 Apr 2021 17:46:14 GMT Content-Length: 58 Content-Type: text/plain; charset=utf-8 status: available environment: development version: 1.0.0 ## API Versioning There are two comon approaches to doing this : 1. By prefixing all URLs with your API version, like **/v1/healthcheck** or **/v2/healthcheck** 2. By using custom **Accept** and **Content-Type** headers on requests and responses to convey the API version, like **Accept: application/vnd.greenlight-v1** From an HTTP semantics point of view, using headers to convey the API version is the 'purer' approach. But from a user-experience point of view, using a URL prefix is arguably better. It makes it possible for developers to see which version of the API is being used at a glance, and it also means that the API can still be explored using a regular web browser (which is harder if custom headers are required). ## Additional Information ### How different Go Types are encoded The following table summarizes how different Go types are mapped to JSON data types during encoding : | Go type | JSON type | |---------------------------------------------------|----------------------------| | bool | JSON boolean | | string | JSON string | | int*, uint*, float*, rune | JSON number | | array, slice | JSON array | | struct, map | JSON object | | nil pointers, interface values, slices, maps, etc | JSON null | | chan, func, complex* | Not supported | | time.Time | RFC3339-format JSON string | | []byte | Base64-encoded JSON string | The last two of these are special cases which deserve a bit more explanation : - Go **time.Time** values (which are actually a struct behind the scenes) will be encoded as a JSON string in RFC 3339 format like **"2020-11-08T06:27:59+01:00"**, rather than as a JSON object. - A **[]byte** slice will be encoded as a base64-encoded JSON string, rather than as a JSON array. So, for example, a byte slice of **[]byte{'h','e','l','l','o'}** would appear as **"aGVsbG8="** in the JSON output. The base64 encoding uses padding and the standard character set. A few other important things to mention : - Encoding of nested objects is supported. So, for example, if you have a slice of structs in Go that will encode to an *array of objects* in JSON. - 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. ### Advanced JSON Customization _When Go is encoding a particular type to JSON, it looks to see if the type has a **MarshalJSON()** method implemented on it. If it has, then Go will call this method to determine how to encode it._ Strictly speaking, when Go is encoding a particular type to JSON it looks to see if the type satisfies the json.Marshaler interface, which looks like this : `type Marshaler interface { MarshalJSON() ([]byte, error) }` If the type does satisfy the interface, then Go will call its **MarshalJSON()** method and use the []byte slice that it returns as the encoded JSON value. If the type doesn't have a **MarshalJSON()** method, then Go will fall back to trying to encode it to JSON based on its own internal set of rules. So, if we want to customize how something is encoded, all we need to do is implement a **MarshalJSON()** method on it which returns a _custom JSON representation of itself_ in a **[]byte** slice. An example is available here : **internal/data/runtime.go** ### System-generated error responses In certain scenarios Go's **http.Server** may still automatically generate and send plain-text HTTP responses. These scenarios include when : - The HTTP request specifies an unsupported HTTP protocol version. - The HTTP request contains a missing or invalid **Host** header, or multiple **Host** headers. - The HTTP request contains an empty **Content-Length** header. - The HTTP request contains an unsupported **Transfer-Encoding** header. - The size of the HTTP request headers exceeds the server's **MaxHeaderBytes** setting. - The client makes a HTTP request to an HTTPS server. For example, if we try sending a request with an invalid **Host** header value, we will get a response like this: `$ curl -i -H "Host: こんにちは" http://localhost:4000/v1/healthcheck HTTP/1.1 400 Bad Request: malformed Host header Content-Type: text/plain; charset=utf-8 Connection: close 400 Bad Request: malformed Host header` Unfortunately, these responses are hard-coded into the Go Standard library, and there's nothing we can do to customize them to use JSON instead. But while this is something to be aware of, it's not necessarily something to worry about. In a production environment it's relatively unlikely that well-behaved, non-malicious, clients would trigger these responses anyway, and we shouldn't be overly concerned if bad clients are sometimes set a plain-text response instead of JSON. ### Panic recovery in other goroutines It's important to realize that our middleware will only recover panics that happen in the _same goroutine that executed the **recoverPanic()** middleware_. If, for example, you have a handler which spins up another goroutine (e.g. to do some background processing), then any panics that happen in the background goroutine will not be recovered - not by the **recoverPanic()** middleware... and not by the panic recovery build into **http.Server**. These panics will cause your application to exit and bring down the server. So, if you are spinning up additional goroutines from within your handlers and there is any chance of a panic, you **must make sure** that you recover any panics from within those goroutines too. A demonstration will follow when we will use a background goroutine to send welcome emails to our API users. ### 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.