Nuxt 5 server-side development with Nitro v3, h3 v2, API routes, middleware, and database integration. Use when creating server routes, integrating D1/Drizzle, or migrating from Nitro v2.
SKILL.md
Nuxt 5 Server Development
Server routes, API patterns, and backend development with Nitro v3.
Use when: creating server API routes, implementing server middleware, integrating databases (D1, PostgreSQL, Drizzle), handling file uploads, migrating from Nitro v2/h3 v1 to Nitro v3/h3 v2, or building backend logic.
Quick Reference
Nuxt 5 Server API Changes (from Nuxt 4)
Area
Nuxt 4 (Nitro v2)
Nuxt 5 (Nitro v3)
Package
nitropack
nitro
h3 imports
import { ... } from 'h3'
import { ... } from 'nitro/h3'
Error creation
createError({statusCode})
new HTTPError({status})
Event path
event.path
event.url.pathname
Event method
event.method
event.req.method
Status code
event.node.res.statusCode
event.res.status
Response headers
setResponseHeader(event, ...)
event.res.headers.set(...)
Runtime config
useRuntimeConfig(event)
useRuntimeConfig()
Route rules redirect
statusCode
status
File-Based Server Routes
server/
├── api/ # API endpoints (/api/*)
│ ├── users/
│ │ ├── index.get.ts → GET /api/users
│ │ ├── index.post.ts → POST /api/users
│ │ ├── [id].get.ts → GET /api/users/:id
│ │ ├── [id].put.ts → PUT /api/users/:id
│ │ └── [id].delete.ts → DELETE /api/users/:id
│ └── health.get.ts → GET /api/health
├── routes/ # Non-API routes
│ └── sitemap.xml.get.ts → GET /sitemap.xml
├── middleware/ # Server middleware
├── plugins/ # Nitro plugins
└── utils/ # Server utilities
The h3 v1 helper functions (auto-imported) still work for request reading:
// These continue to work (auto-imported)
const id = getRouterParam(event, 'id')
const query = getQuery(event)
const body = await readBody(event)
const cookie = getCookie(event, 'name')
But for setting responses, prefer the Web Standard API:
// Prefer v5 style
event.res.status = 201
event.res.headers.set('Cache-Control', 'max-age=3600')
// Still works but deprecated for setting
setResponseStatus(event, 201)
setHeader(event, 'Cache-Control', 'max-age=3600')
Error Handling (v5 Change)
Server-Side: HTTPError
In server routes, use HTTPError instead of createError:
// Nuxt 5 server routes
import { HTTPError } from 'nitro/h3'
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
if (!id) {
throw new HTTPError({ status: 400, statusText: 'User ID is required' })
}
const user = await findUser(id)
if (!user) {
throw new HTTPError({ status: 404, statusText: 'Not Found' })
}
return user
})
App-Side: createError (Unchanged)
In the Vue part of your app (app/ directory), createError continues to work:
// app/ code - createError still works
throw createError({
statusCode: 404,
statusMessage: 'Page Not Found',
fatal: true
})
Validation Errors
import { z } from 'zod'
import { HTTPError } from 'nitro/h3'
const createUserSchema = z.object({
name: z.string().min(2).max(100),
email: z.email()
})
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const result = createUserSchema.safeParse(body)
if (!result.success) {
throw new HTTPError({
status: 400,
statusText: 'Validation failed',
})
}
return { success: true, data: result.data }
})
Request Utilities
URL Parameters
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
if (!id) {
throw new HTTPError({ status: 400, statusText: 'User ID is required' })
}
return { id }
})