---
title: "Server routes"
description: "Write API routes with nuxt/server and validate input with valibot."
canonical_url: "https://wervt.app/guides/server-routes"
---
# Server routes

> Write API routes with nuxt/server and validate input with valibot.

Routes live in `server/api/` and use the helpers from `nuxt/server`. Nothing is auto-imported on
the server, so import what you use.

```ts [server/api/todos/[id].patch.ts]
import { useDb, useValidatedBody, useValidatedParams, vh } from '@wervt/nuxt/server'
import { eq } from 'drizzle-orm'
import { createError, defineEventHandler } from 'nuxt/server'
import * as v from 'valibot'
import { todos } from '../../db/schema'

export default defineEventHandler(async (event) => {
  const { id } = await useValidatedParams(event, { id: vh.id })
  const values = await useValidatedBody(event, {
    title: v.optional(v.pipe(v.string(), v.trim(), v.nonEmpty(), v.maxLength(200))),
    done: v.optional(v.boolean()),
  })
  const [todo] = await useDb().update(todos).set(values).where(eq(todos.id, id)).returning()
  if (!todo) throw createError({ status: 404, message: 'Todo not found' })
  return todo
})
```

## Validation

`useValidatedBody`, `useValidatedQuery` and `useValidatedParams` take a valibot schema, or a plain
object of schemas, and return typed data. Invalid input is answered with a 400 that lists the issues
per field:

```json
{
  "status": 400,
  "message": "Validation failed",
  "data": { "nested": { "id": ["Invalid digits: Received \"abc\""] } }
}
```

Query values and route params are always strings. Convert them with the
[`vh` helpers](https://wervt.app/api/validation#vh): `vh.id` for ids, `vh.intAsString`, `vh.numAsString`,
`vh.boolAsString` and `vh.checkboxAsString`.

<tip>

Need to handle invalid input yourself? `useSafeValidatedBody` and friends return
`{ success, data }` or `{ success: false, issues }` instead of throwing.

</tip>

## Everything else

`nuxt/server` also has cookies (`getCookie`, `setCookie`), sessions (`useSession`), redirects,
CORS and `getRequestURL`. Prefer it over `h3` or `nitro/h3`: it is Nuxt's stable server API and
stays the same when Nitro changes underneath.


## Sitemap

See the full [sitemap](https://wervt.app/sitemap.md) for all pages.
