Params, query and body in handlers
An API handler needs data from the request: the post number from the address, a search string after the question mark, form fields. Nitro provides getRouterParam, getQuery and readBody for this, and createError answers the client with an error and the right status code.
A shared list of posts
Several handlers will work with one list. Create server/utils/posts.ts:
// server/utils/posts.ts
export const posts = [
{ id: 1, title: 'First post', text: 'Hello, world!' },
{ id: 2, title: 'Learning Nuxt', text: 'Pages from files.' }
]Everything exported from server/utils/ is available in handlers without an import. The list lives in server memory and resets on restart; in the next lesson posts move to disk.
A route parameter
Create server/api/posts/[id].get.ts:
// server/api/posts/[id].get.ts
export default defineEventHandler((event) => {
const id = Number(getRouterParam(event, 'id'))
const post = posts.find((p) => p.id === id)
if (!post) {
throw createError({ statusCode: 404, message: 'Post not found' })
}
return post
})getRouterParam returns the part of the address in square brackets. /api/posts/1 returns the first post, and /api/posts/99 returns a 404 response with the message "Post not found".
The query string
Add search to server/api/posts.get.ts:
// server/api/posts.get.ts
export default defineEventHandler((event) => {
const { search } = getQuery(event)
if (typeof search === 'string' && search !== '') {
const word = search.toLowerCase()
return posts.filter((p) => p.title.toLowerCase().includes(word))
}
return posts
})getQuery returns an object with the parameters after ?. The address /api/posts?search=nuxt returns only the post "Learning Nuxt".
The request body
A new post arrives in the body of a POST request. Create server/api/posts.post.ts:
// server/api/posts.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody<{ title?: string; text?: string }>(event)
if (!body.title || !body.text) {
throw createError({ statusCode: 400, message: 'Fill in the title and the text' })
}
const post = {
id: Math.max(0, ...posts.map((p) => p.id)) + 1,
title: body.title,
text: body.text
}
posts.push(post)
setResponseStatus(event, 201)
return post
})readBody reads the request body and parses the JSON. Status 201 means "created". Test the handler from a terminal:
curl -X POST http://localhost:3000/api/posts -H "Content-Type: application/json" -d "{\"title\":\"Third post\",\"text\":\"Some text\"}"The response is the new post with id 3, and it appears at /api/posts. You will build a form on the site for this request in the data fetching chapter.
Request functions
| Function | What it returns |
|---|---|
getRouterParam(event, 'id') | A route parameter, as a string |
getQuery(event) | An object of parameters after ? |
await readBody(event) | The request body |
getHeader(event, 'user-agent') | A request header |
getCookie(event, 'token') | A cookie value |
Common mistake
readBody returns a promise. If you forget await, the variable holds a Promise, which has no title field, and the check returns a 400 error even for a valid request. Make the handler async and always write await readBody(event).