> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gocopper.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# JSON APIs

Copper provides `chttp.JSONReaderWriter` to read, validate, and write JSON. Add it to your router's params and wire will inject it for you:

```go theme={null}
type NewRouterParams struct {
	Rockets *Svc
	RW      *chttp.JSONReaderWriter
	Logger  clogger.Logger
}
```

<Note>
  `copper scaffold:router` generates a router with only a `Logger`. Add the `RW` field yourself when your router serves JSON.
</Note>

### Reading JSON

`ReadJSON` decodes the request body into a struct. If the body is empty, malformed, or fails validation, the `400 Bad Request` response will automatically be written for you and `ReadJSON` returns `false`, so your handler only needs to return:

```go theme={null}
func (ro *Router) HandleLaunchRocket(w http.ResponseWriter, r *http.Request) {
	var body struct {
		Destination string `json:"destination"`
	}

	if ok := ro.rw.ReadJSON(w, r, &body); !ok {
		return
	}

	// ...
}
```

### Validating

`ReadJSON` validates the body with [govalidator](https://github.com/asaskevich/govalidator) `valid:` tags before your handler sees it:

```go theme={null}
var body struct {
	Destination string  `json:"destination" valid:"required"`
	Contact     string  `json:"contact"     valid:"required,email"`
	Mode        string  `json:"mode"        valid:"in(orbital|suborbital)"`
	Payload     *string `json:"payload"     valid:"length(1|64)"`
}
```

For your own domain types, you may register a custom validator once and use it like any other tag:

```go theme={null}
func init() {
	govalidator.CustomTypeTagMap.Set("launchpad", func(i any, _ any) bool {
		pad, ok := i.(Launchpad)
		return ok && pad.Valid()
	})
}
```

```go theme={null}
Pad Launchpad `json:"pad" valid:"required,launchpad"`
```

Of course, some rules span multiple fields and don't fit in tags. You may check these at the top of your handler and respond early:

```go theme={null}
if body.Mode == "orbital" && body.Payload == nil {
	ro.rw.WriteJSON(w, chttp.WriteJSONParams{
		StatusCode: http.StatusBadRequest,
		Data:       map[string]string{"error": "orbital launches require a payload"},
	})
	return
}
```

### Writing JSON

`WriteJSON` marshals your data and sets the `Content-Type` header. The status code defaults to `200 OK`:

```go theme={null}
ro.rw.WriteJSON(w, chttp.WriteJSONParams{Data: launch})

ro.rw.WriteJSON(w, chttp.WriteJSONParams{
	StatusCode: http.StatusCreated,
	Data:       launch,
})
```

If `Data` is an `error`, it will be encoded as `{"error": "<message>"}`, which is convenient for 4xx responses.

For a bare `401`, you may use the `Unauthorized` shorthand:

```go theme={null}
ro.rw.Unauthorized(w)
```

### Putting It Together

Let's put it all together in a complete handler that reads and validates the body, calls the service, maps known errors to status codes, and responds:

```go theme={null}
func (ro *Router) HandleLaunchRocket(w http.ResponseWriter, r *http.Request) {
	var (
		ctx  = r.Context()
		id   = chttp.URLParams(r)["id"]
		body struct {
			Destination string `json:"destination" valid:"required"`
		}
	)

	if ok := ro.rw.ReadJSON(w, r, &body); !ok {
		return
	}

	launch, err := ro.rockets.Launch(ctx, id, body.Destination)
	switch {
	case errors.Is(err, ErrRocketNotFound):
		ro.rw.WriteJSON(w, chttp.WriteJSONParams{
			StatusCode: http.StatusNotFound,
			Data:       map[string]string{"error": "rocket not found"},
		})
		return
	case errors.Is(err, ErrAlreadyLaunched):
		ro.rw.WriteJSON(w, chttp.WriteJSONParams{
			StatusCode: http.StatusConflict,
			Data:       map[string]string{"error": "rocket has already launched"},
		})
		return
	case err != nil:
		ro.logger.Error("Failed to launch rocket", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	ro.rw.WriteJSON(w, chttp.WriteJSONParams{
		StatusCode: http.StatusCreated,
		Data:       launch,
	})
}
```

Sentinel errors like `ErrRocketNotFound` are declared in the service package with `errors.New`. To learn more, see [Error Handling](/the-basics/error-handling).
