The request body
The request body is the data a client sends with POST, PUT or PATCH: a new post, a comment, a file. Hono reads the body asynchronously, so the handler is declared async and uses await.
JSON
An API usually accepts JSON. Add a route for creating a post in src/index.ts:
// src/index.ts
app.post('/posts', async (c) => {
const data = await c.req.json()
return c.json({ received: data }, 201)
})Send a request from the terminal:
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-d '{"title": "New post", "body": "Text"}'The server returns the object you sent inside the received field, with code 201.
Saving the post
Now add the post to the posts array:
// src/index.ts
type NewPost = { title: string; body: string }
app.post('/posts', async (c) => {
const data = await c.req.json<NewPost>()
const post = { id: posts.length + 1, title: data.title, body: data.body }
posts.push(post)
return c.json(post, 201)
})The generic type in c.req.json<NewPost>() only tells TypeScript which fields to expect. It checks nothing: the client can send anything. Real validation comes in the zod lesson. After the request, GET /posts shows three posts — until the server restarts, because the array lives in memory.
Form data
An HTML form sends data not as JSON but as application/x-www-form-urlencoded or multipart/form-data. Both are handled by c.req.parseBody():
// src/index.ts
app.post('/comments', async (c) => {
const body = await c.req.parseBody()
const author = String(body.author ?? '')
const text = String(body.text ?? '')
return c.text(`${author} writes: ${text}`)
})Check it like this:
curl -X POST http://localhost:3000/comments -d "author=Anna" -d "text=Great post"The response is "Anna writes: Great post". A field value from parseBody() is a string, or a File object if the form uploaded a file.
Other ways
| Method | When to use |
|---|---|
c.req.json() | a JSON body |
c.req.parseBody() | a regular HTML form |
c.req.formData() | you need a standard FormData object |
c.req.text() | the body as a string |
c.req.arrayBuffer() | binary data |
// src/index.ts
app.post('/upload', async (c) => {
const form = await c.req.formData()
const file = form.get('cover')
return c.text(file instanceof File ? `File ${file.name}` : 'No file')
})formData() returns the same FormData as in the browser, with get() and getAll() methods.
Common mistake
The body contains invalid JSON: single quotes instead of double ones, a trailing comma, a cut-off string. c.req.json() then throws a parse error and the client gets code 500, even though the client is at fault. Do not trust the request body: the zod validator from the next chapter answers bad data with code 400, and the error lesson shows how to shape the response.