Server directory structure and automatic scanning
Nuxt automatically scans files inside the server/ directory to register API and server handlers with Hot Module Replacement (HMR) support. The server/ directory contains subdirectories: api/ (automatically prefixed with /api in routes), routes/ (for routes without /api prefix), middleware/ (for server middleware), plugins/ (for Nitro plugins), utils/ (for server utility helpers), and types/ (for server-only types).
Server handler definition requirement
Each file in the server/ directory should export a default function defined with defineEventHandler() or eventHandler() (which is an alias). The handler can directly return JSON data, a Promise, or a Response object.
Server Routes - /api prefix
Files inside ~~/server/api are automatically prefixed with /api in their route. For example, a file server/api/hello.ts becomes accessible at /api/hello.
Server Routes - routes directory without prefix
To add server routes without /api prefix, put them into ~~/server/routes directory. For example, server/routes/hello.ts becomes accessible at /hello.
Server middleware purpose and behavior
Nuxt automatically reads any file in ~~/server/middleware to create server middleware. Middleware handlers run on every request before any other server route to add or check headers, log requests, or extend the event's request object. Middleware handlers should not return anything, nor close or respond to the request, and should only inspect or extend the request context or throw an error.
Server middleware example - logging
A server middleware can log requests using getRequestURL(event): export default defineEventHandler((event) => { console.log('New request: ' + getRequestURL(event)) })
Server middleware example - extending context
A server middleware can extend the event context: export default defineEventHandler((event) => { event.context.auth = { user: 123 } })
Server plugins registration
Nuxt automatically reads any files in ~~/ server/plugins directory and registers them as Nitro plugins. This allows extending Nitro's runtime behavior and hooking into lifecycle events. Plugins should use definePlugin from 'nitro'.
Server utilities directory
Server routes are powered by h3 which comes with helper functions. You can add more helpers yourself inside the ~~/server/utils directory. Custom handler utilities can wrap the original handler and perform additional operations before returning the final response.
Server alias #server
The #server alias can be used to import files from anywhere within the server/ directory, regardless of the importing file's location. For example: import { formatUser } from '#server/utils/formatUser'. This alias ensures consistent imports across server code. The #server alias can only be used within the server/ directory; importing from #server in client code will result in an error.
Server types auto-import
Types placed in ~~/ server/types/ are auto-imported in the server context only and can be referenced in server routes, middleware, plugins, and utilities without importing them. Only files directly in server/types/ are scanned; files in nested subdirectories are not auto-imported. Types that are also needed in the Vue app belong in shared/types/ instead.
Dynamic route parameters in server routes
Server routes can use dynamic parameters within brackets in the file name like /api/hello/[name].ts and be accessed via getRouterParam(event, 'name'). For example, a request to /api/hello/nuxt would pass 'nuxt' as the name parameter.
HTTP method matching in server routes
Handle filenames can be suffixed with .get, .post, .put, .delete, etc. to match the request's HTTP method. For example, server/api/test.get.ts handles GET requests and server/api/test.post.ts handles POST requests to the same /api/test endpoint. Any other method returns a 405 error. You can also use index.[method].ts inside a directory for structuring code differently.
Catch-all routes in server
Catch-all routes are created with [...].ts or [...slug].ts in the file name. For example, ~~/server/api/foo/[...].ts registers a catch-all route for all requests that do not match any route handler, such as /api/foo/bar/baz. Access the route path via event.context.path and the route segment via event.context.params._ or event.context.params.slug if named.
Reading request body in server routes
To read the request body in a server route, use readBody(event): export default defineEventHandler(async (event) => { const body = await readBody(event); return { body } }). This should only be used in routes that accept body data (typically POST/PUT methods). Using readBody in a GET request will throw a 405 Method Not Allowed HTTP error.
Query parameters in server routes
To access query parameters in server routes, use getQuery(event): export default defineEventHandler((event) => { const query = getQuery(event); return { a: query.foo, b: query.baz } }). For a query like /api/query?foo=bar&baz=qux, this returns { a: 'bar', b: 'qux' }.
Error handling in server routes
If no errors are thrown, a status code of 200 OK is returned. Any uncaught errors return a 500 Internal Server Error. To return other error codes, throw an exception with createError({ status: 400, statusText: 'message' }). Example: if (!Number.isInteger(id)) { throw createError({ status: 400, statusText: 'ID should be an integer' }) }
Setting response status codes in server routes
To return other status codes besides 200 or 500, use the setResponseStatus utility: setResponseStatus(event, 202). For example, to return 202 Accepted in a server route.
Using useRuntimeConfig in server routes
useRuntimeConfig() can be called in server routes to access runtime configuration. Example: const config = useRuntimeConfig(); then use config.githubToken or other configured values.
Reading request cookies in server routes
To read cookies from the request in server routes, use parseCookies(event): export default defineEventHandler((event) => { const cookies = parseCookies(event); return { cookies } })
Forwarding context and headers in server routes
By default, neither the headers from the incoming request nor the request context are forwarded when making fetch requests in server routes. Use event.$fetch to forward the request context and headers: return event.$fetch('/api/forwarded'). Headers that are not meant to be forwarded (transfer-encoding, connection, keep-alive, upgrade, expect, host, accept) will not be included.
Awaiting promises after response in server routes
Use event.waitUntil to await a promise in the background without delaying the response to the client. This is useful for asynchronous tasks that shouldn't block the response, such as caching and logging. event.waitUntil(timeConsumingBackgroundTask()); return 'done' will immediately send the response while the background task completes.
Nitro configuration in nuxt.config
Use the nitro key in nuxt.config.ts to directly set Nitro configuration. This is an advanced option and custom config can affect production deployments. Example: export default defineNuxtConfig({ nitro: {} })
Nested router in server routes
Create nested routers using h3: import { createRouter, defineEventHandler, useBase } from 'h3'. Create a router instance, define routes on it, and export with useBase('/api/hello', router.handler).
Sending streams from server routes
To send file streams from server routes, use sendStream: import { sendStream } from 'h3'; export default defineEventHandler((event) => { return sendStream(event, fs.createReadStream('/path/to/file')) }). This is an experimental feature available in all environments.
Sending redirects from server routes
To send a redirect response from server routes, use sendRedirect: export default defineEventHandler(async (event) => { await sendRedirect(event, '/path/redirect/to', 302) })
Legacy handler or middleware support
Legacy Node.js-style handlers and middleware can be used with fromNodeMiddleware from h3. For handlers: export default fromNodeMiddleware((req, res) => { res.end('Legacy handler') }). For middleware: export default fromNodeMiddleware((req, res, next) => { console.log('Legacy middleware'); next() }). Never combine next() callback with an async legacy middleware or one that returns a Promise.
Server storage configuration with Redis
Configure storage mount points using nitro.storage in nuxt.config.ts. Example Redis configuration: nitro: { storage: { redis: { driver: 'redis', port: 6379, host: '127.0.0.1', username: '', password: '', db: 0, tls: {} } } }
Using server storage in API handlers
Access configured storage in API handlers using useStorage: const keys = await useStorage('redis').getKeys(); await useStorage('redis').setItem('foo', 'bar'); await useStorage('redis').removeItem('foo');
Server storage configuration via plugin
Configure storage dynamically using a server plugin: import { definePlugin } from 'nitro'; import redisDriver from 'unstorage/drivers/redis'; export default definePlugin(() => { const storage = useStorage(); const driver = redisDriver({ base: 'redis', host: useRuntimeConfig().redis.host, port: useRuntimeConfig().redis.port }); storage.mount('redis', driver) })
Cannot mix Vue and Nitro code
Do not import Vue app code (components, composables, or other app-only utilities) in server routes or utilities, and do not import server-only code in your app. Server code and client code must remain separated.