Building Server API Routes in Nuxt 3: A Practical Guide
Nuxt 3 exposes files in server/api as HTTP endpoints. Learn file routing, request handling, and common pitfalls for building backend logic inside your Nuxt application.
20 Jan 2026, 16:35 UTC

The Quick Answer
Nuxt 3 automatically exposes files in server/api as HTTP endpoints. Name a file server/api/users.ts and it becomes /api/users. Use this to build backend logic without a separate server.
How the File-to-Route Mapping Works
Nuxt 3's Nitro server engine scans the server/api directory during build. Each file maps directly to a route:
server/api/hello.ts→GET /api/helloserver/api/users/[id].ts→GET /api/users/123server/api/auth/post.ts→POST /api/auth
Basic Route Handler Example
Create server/api/test.ts:
export default defineEventHandler(() => {
return { message: 'Hello from server!' }
})
This handler receives a H3 event object and returns JSON. Access it at http://localhost:3000/api/test.
Handling Different HTTP Methods
Export named handlers for specific methods:
export default defineEventHandler(() => ({ message: 'Default GET' }))
export const POST = defineEventHandler(async (event) => {
const body = await readBody(event)
return { received: body }
})
Now GET /api/test returns the default, while POST /api/test echoes the request body.
Accessing Route Parameters
For dynamic routes like /api/users/42, create server/api/users/[id].ts:
export default defineEventHandler((event) => {
const { id } = event.context.params
return { userId: id }
})
Note: Use event.context.params, not event.query. The latter contains query string parameters.
Reading Request Data
Use H3 helper functions:
export const POST = defineEventHandler(async (event) => {
const body = await readBody(event) // JSON body
const query = getQuery(event) // ?foo=bar → { foo: 'bar' }
const headers = getHeaders(event) // Request headers
return { body, query, headers }
})
Environment Variables and Secrets
Server routes run in Node.js, not the browser. Access environment variables directly:
export default defineEventHandler(() => {
const dbUrl = process.env.DATABASE_URL
return { dbConfigured: !!dbUrl }
})
This is safe—process.env values never reach client-side code.
Sharing Logic with Composables
Place reusable functions in server/composables and import them:
// server/composables/db.ts
export const db = { find: (id) => ({ id, name: 'User ' + id }) }
// server/api/users/[id].ts
import { db } from '../composables/db'
export default defineEventHandler((event) => {
const { id } = event.context.params
return db.find(id)
})
Limitations and Common Mistakes
- Browser globals fail: Never use
window,document, orlocalStoragein server routes—they don't exist in Node.js. - Heavy imports increase cold start: Importing large libraries like
lodashbloats server bundles. Use tree-shakable alternatives or import specific functions. - Hot reload quirks: Some versions require restarting
nuxt devafter adding new route files. - Prerendering issues: Static site generation may skip server routes. Test with
nuxt buildand check.output/server/routes.
Verifying Your Routes
After creating a route, verify it works:
- Start dev server:
npm run devornuxt dev - Request your endpoint:
curl http://localhost:3000/api/test - Check Nitro's route table in
.nuxt/nitro/routes.json(or.output/server/routes.jsonafter build)
Configuring Route Rules
Use nitro in nuxt.config.ts to apply caching, CORS, or authentication:
export default defineNuxtConfig({
nitro: {
routeRules: {
'api/users/**': { cors: true, static: false },
'api/public/**': { swr: 60 } // Stale-while-revalidate
}
}
})
Conclusion
Nuxt 3's server/api directory lets you build full backend APIs alongside your frontend. The file-system routing is intuitive, but remember: these handlers run exclusively on the server. Test with nuxt dev, verify route generation, and avoid browser-only code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.