Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

FunctionWhat 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).

Thanks for reading!

Donate to the site — we work hard for you.