# Body parser

> Decode a JSON body into a typed value with tinka.Decode and report every invalid field at once.

This guide covers reading request bodies in Tinka. You will learn how to:

- Decode a JSON request body into a typed value
- Validate the input and report every invalid field at once
- Know which status each failure returns, and the limits that protect the server

## Overview

`tinka.Decode` reads a JSON request body into a value of the type you name, checks it with your validation, and turns every failure into a response the client can act on. It also guards the server: bodies are capped at 1 MiB and must arrive within 30s, so a client cannot exhaust memory or hold a request open.

## Decode a body

Name the type you expect and pass the response writer and the request. On failure, return the error: it's already the right response.

```go title="posts.go"
package main

import (
	"net/http"

	"github.com/mucolabs/tinka"
)

type newPost struct {
	Title string `json:"title"`
	Body  string `json:"body"`
}

func createPost(w http.ResponseWriter, r *http.Request) error {
	in, err := tinka.Decode[newPost](w, r)
	if err != nil {
		return err
	}
	return tinka.JSON(w, http.StatusCreated, in)
}
```

`Decode` answers with these statuses, each as [problem details](exception-handling.md):

- **415**: the `Content-Type` isn't JSON (`application/json` or a `+json` type)
- **400**: the body is empty, malformed, or holds more than one JSON value
- **413**: the body is larger than 1 MiB (`tinka.MaxBodyBytes`)
- **408**: the body took longer than 30s to arrive
- **422**: a field has the wrong type, or your validation found problems

## Validate the input

Give the type a `Valid` method and `Decode` calls it after decoding. It returns a problem for each invalid field, keyed by the field's name; an empty map means the input is valid. Add `context` and `strings` to the file's imports, then the method:

```go title="posts.go" ins={1-11}
func (p newPost) Valid(ctx context.Context) map[string]string {
	problems := map[string]string{}
	if strings.TrimSpace(p.Title) == "" {
		problems["title"] = "Enter a title."
	}
	if len(p.Body) > 10_000 {
		problems["body"] = "Keep the body under 10,000 characters."
	}
	return problems
}
```

Every invalid field comes back in one `422` response, so a form can show all its errors at once:

```json title="Response"
{
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "Some fields are invalid.",
  "code": "invalid",
  "fields": {
    "title": "Enter a title."
  },
  "request_id": "S5QWQ3DAEXTVZ6M7F4DZEL6EBI"
}
```

:::tip
Keep `Valid` to checks on the input itself: required fields, lengths, formats. A rule that needs storage, such as "this email address is free", belongs in the Action that changes state, which returns an error the handler maps to a response.
:::

See also: [Exception handling](exception-handling.md), [App](../../reference/app.md)
