mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-14 18:13:54 +00:00
509 lines
13 KiB
Markdown
509 lines
13 KiB
Markdown
---
|
|
title: server
|
|
head.title: 'server/'
|
|
description: The server/ directory is used to register API and server handlers to your application.
|
|
navigation.icon: i-ph-folder-duotone
|
|
---
|
|
|
|
Nuxt automatically scans files inside these directories to register API and server handlers with Hot Module Replacement (HMR) support.
|
|
|
|
```bash [Directory structure]
|
|
-| server/
|
|
---| api/
|
|
-----| hello.ts # /api/hello
|
|
---| routes/
|
|
-----| bonjour.ts # /bonjour
|
|
---| middleware/
|
|
-----| log.ts # log all requests
|
|
```
|
|
|
|
Each file should export a default function defined with `defineEventHandler()` or `eventHandler()` (alias).
|
|
|
|
The handler can directly return JSON data, a `Promise`, or use `event.node.res.end()` to send a response.
|
|
|
|
```ts twoslash [server/api/hello.ts]
|
|
export default defineEventHandler((event) => {
|
|
return {
|
|
hello: 'world'
|
|
}
|
|
})
|
|
```
|
|
|
|
You can now universally call this API in your pages and components:
|
|
|
|
```vue [pages/index.vue]
|
|
<script setup lang="ts">
|
|
const { data } = await useFetch('/api/hello')
|
|
</script>
|
|
|
|
<template>
|
|
<pre>{{ data }}</pre>
|
|
</template>
|
|
```
|
|
|
|
## Server Routes
|
|
|
|
Files inside the `~/server/api` are automatically prefixed with `/api` in their route.
|
|
|
|
To add server routes without `/api` prefix, put them into `~/server/routes` directory.
|
|
|
|
**Example:**
|
|
|
|
```ts [server/routes/hello.ts]
|
|
export default defineEventHandler(() => 'Hello World!')
|
|
```
|
|
|
|
Given the example above, the `/hello` route will be accessible at <http://localhost:3000/hello>.
|
|
|
|
::note
|
|
Note that currently server routes do not support the full functionality of dynamic routes as [pages](/docs/guide/directory-structure/pages#dynamic-routes) do.
|
|
::
|
|
|
|
## Server Middleware
|
|
|
|
Nuxt will automatically read in any file in the `~/server/middleware` to create server middleware for your project.
|
|
|
|
Middleware handlers will run on every request before any other server route to add or check headers, log requests, or extend the event's request object.
|
|
|
|
::note
|
|
Middleware handlers should not return anything (nor close or respond to the request) and only inspect or extend the request context or throw an error.
|
|
::
|
|
|
|
**Examples:**
|
|
|
|
```ts [server/middleware/log.ts]
|
|
export default defineEventHandler((event) => {
|
|
console.log('New request: ' + getRequestURL(event))
|
|
})
|
|
```
|
|
|
|
```ts [server/middleware/auth.ts]
|
|
export default defineEventHandler((event) => {
|
|
event.context.auth = { user: 123 }
|
|
})
|
|
```
|
|
|
|
## Server Plugins
|
|
|
|
Nuxt will automatically read any files in the `~/server/plugins` directory and register them as Nitro plugins. This allows extending Nitro's runtime behavior and hooking into lifecycle events.
|
|
|
|
**Example:**
|
|
|
|
```ts [server/plugins/nitroPlugin.ts]
|
|
export default defineNitroPlugin((nitroApp) => {
|
|
console.log('Nitro plugin', nitroApp)
|
|
})
|
|
```
|
|
|
|
:read-more{to="https://nitro.unjs.io/guide/plugins" title="Nitro Plugins" target="_blank"}
|
|
|
|
## Server Utilities
|
|
|
|
Server routes are powered by [unjs/h3](https://github.com/unjs/h3) which comes with a handy set of helpers.
|
|
|
|
:read-more{to="https://www.jsdocs.io/package/h3#package-index-functions" title="Available H3 Request Helpers" target="_blank"}
|
|
|
|
You can add more helpers yourself inside the `~/server/utils` directory.
|
|
|
|
For example, you can define a custom handler utility that wraps the original handler and performs additional operations before returning the final response.
|
|
|
|
**Example:**
|
|
|
|
```ts [server/utils/handler.ts]
|
|
import type { EventHandler, EventHandlerRequest } from 'h3'
|
|
|
|
export const defineWrappedResponseHandler = <T extends EventHandlerRequest, D> (
|
|
handler: EventHandler<T, D>
|
|
): EventHandler<T, D> =>
|
|
defineEventHandler<T>(async event => {
|
|
try {
|
|
// do something before the route handler
|
|
const response = await handler(event)
|
|
// do something after the route handler
|
|
return { response }
|
|
} catch (err) {
|
|
// Error handling
|
|
return { err }
|
|
}
|
|
})
|
|
```
|
|
|
|
## Server Types
|
|
|
|
::tip
|
|
This feature is available from Nuxt >= 3.5
|
|
::
|
|
|
|
To improve clarity within your IDE between the auto-imports from 'nitro' and 'vue', you can add a `~/server/tsconfig.json` with the following content:
|
|
|
|
```json [server/tsconfig.json]
|
|
{
|
|
"extends": "../.nuxt/tsconfig.server.json"
|
|
}
|
|
```
|
|
|
|
Currently, these values won't be respected when type checking ([`nuxi typecheck`](/docs/api/commands/typecheck)), but you should get better type hints in your IDE.
|
|
|
|
## Recipes
|
|
|
|
### Route Parameters
|
|
|
|
Server routes can use dynamic parameters within brackets in the file name like `/api/hello/[name].ts` and be accessed via `event.context.params`.
|
|
|
|
```ts [server/api/hello/[name\\].ts]
|
|
export default defineEventHandler((event) => {
|
|
const name = getRouterParam(event, 'name')
|
|
|
|
return `Hello, ${name}!`
|
|
})
|
|
```
|
|
|
|
::tip
|
|
Alternatively, use `getValidatedRouterParams` with a schema validator such as Zod for runtime and type safety.
|
|
::
|
|
|
|
You can now universally call this API on `/api/hello/nuxt` and get `Hello, nuxt!`.
|
|
|
|
### Matching HTTP Method
|
|
|
|
Handle file names can be suffixed with `.get`, `.post`, `.put`, `.delete`, ... to match request's [HTTP Method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods).
|
|
|
|
```ts [server/api/test.get.ts]
|
|
export default defineEventHandler(() => 'Test get handler')
|
|
```
|
|
|
|
```ts [server/api/test.post.ts]
|
|
export default defineEventHandler(() => 'Test post handler')
|
|
```
|
|
|
|
Given the example above, fetching `/test` with:
|
|
|
|
- **GET** method: Returns `Test get handler`
|
|
- **POST** method: Returns `Test post handler`
|
|
- Any other method: Returns 405 error
|
|
|
|
You can also use `index.[method].ts` inside a directory for structuring your code differently, this is useful to create API namespaces.
|
|
|
|
::code-group
|
|
```ts [server/api/foo/index.get.ts]
|
|
export default defineEventHandler((event) => {
|
|
// handle GET requests for the `api/foo` endpoint
|
|
})
|
|
```
|
|
```ts [server/api/foo/index.post.ts]
|
|
export default defineEventHandler((event) => {
|
|
// handle POST requests for the `api/foo` endpoint
|
|
})
|
|
```
|
|
```ts [server/api/foo/bar.get.ts]
|
|
export default defineEventHandler((event) => {
|
|
// handle GET requests for the `api/foo/bar` endpoint
|
|
})
|
|
```
|
|
::
|
|
|
|
### Catch-all Route
|
|
|
|
Catch-all routes are helpful for fallback route handling.
|
|
|
|
For example, creating a file named `~/server/api/foo/[...].ts` will register a catch-all route for all requests that do not match any route handler, such as `/api/foo/bar/baz`.
|
|
|
|
```ts [server/api/foo/[...\\].ts]
|
|
export default defineEventHandler((event) => {
|
|
// event.context.path to get the route path: '/api/foo/bar/baz'
|
|
// event.context.params._ to get the route segment: 'bar/baz'
|
|
return `Default foo handler`
|
|
})
|
|
```
|
|
|
|
You can set a name for the catch-all route by using `~/server/api/foo/[...slug].ts` and access it via `event.context.params.slug`.
|
|
|
|
```ts [server/api/foo/[...slug\\].ts]
|
|
export default defineEventHandler((event) => {
|
|
// event.context.params.slug to get the route segment: 'bar/baz'
|
|
return `Default foo handler`
|
|
})
|
|
```
|
|
|
|
### Body Handling
|
|
|
|
```ts [server/api/submit.post.ts]
|
|
export default defineEventHandler(async (event) => {
|
|
const body = await readBody(event)
|
|
return { body }
|
|
})
|
|
```
|
|
|
|
::tip{to="https://unjs.io/blog/2023-08-15-h3-towards-the-edge-of-the-web#runtime-type-safe-request-utils"}
|
|
Alternatively, use `readValidatedBody` with a schema validator such as Zod for runtime and type safety.
|
|
::
|
|
|
|
You can now universally call this API using:
|
|
|
|
```vue [app.vue]
|
|
<script setup lang="ts">
|
|
async function submit() {
|
|
const { body } = await $fetch('/api/submit', {
|
|
method: 'post',
|
|
body: { test: 123 }
|
|
})
|
|
}
|
|
</script>
|
|
```
|
|
|
|
::note
|
|
We are using `submit.post.ts` in the filename only to match requests with `POST` method that can accept the request body. When using `readBody` within a GET request, `readBody` will throw a `405 Method Not Allowed` HTTP error.
|
|
::
|
|
|
|
### Query Parameters
|
|
|
|
Sample query `/api/query?foo=bar&baz=qux`
|
|
|
|
```ts [server/api/query.get.ts]
|
|
export default defineEventHandler((event) => {
|
|
const query = getQuery(event)
|
|
|
|
return { a: query.foo, b: query.baz }
|
|
})
|
|
```
|
|
|
|
::tip{to="https://unjs.io/blog/2023-08-15-h3-towards-the-edge-of-the-web#runtime-type-safe-request-utils"}
|
|
Alternatively, use `getValidatedQuery` with a schema validator such as Zod for runtime and type safety.
|
|
::
|
|
|
|
### Error Handling
|
|
|
|
If no errors are thrown, a status code of `200 OK` will be returned.
|
|
|
|
Any uncaught errors will return a `500 Internal Server Error` HTTP Error.
|
|
|
|
To return other error codes, throw an exception with [`createError`](/docs/api/utils/create-error):
|
|
|
|
```ts [server/api/validation/[id\\].ts]
|
|
export default defineEventHandler((event) => {
|
|
const id = parseInt(event.context.params.id) as number
|
|
|
|
if (!Number.isInteger(id)) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'ID should be an integer',
|
|
})
|
|
}
|
|
return 'All good'
|
|
})
|
|
```
|
|
|
|
### Status Codes
|
|
|
|
To return other status codes, use the [`setResponseStatus`](/docs/api/utils/set-response-status) utility.
|
|
|
|
For example, to return `202 Accepted`
|
|
|
|
```ts [server/api/validation/[id\\].ts]
|
|
export default defineEventHandler((event) => {
|
|
setResponseStatus(event, 202)
|
|
})
|
|
```
|
|
|
|
### Runtime Config
|
|
|
|
::code-group
|
|
```ts [server/api/foo.ts]
|
|
export default defineEventHandler(async (event) => {
|
|
const config = useRuntimeConfig(event)
|
|
|
|
const repo = await $fetch('https://api.github.com/repos/nuxt/nuxt', {
|
|
headers: {
|
|
Authorization: `token ${config.githubToken}`
|
|
}
|
|
})
|
|
|
|
return repo
|
|
})
|
|
```
|
|
```ts [nuxt.config.ts]
|
|
export default defineNuxtConfig({
|
|
runtimeConfig: {
|
|
githubToken: ''
|
|
}
|
|
})
|
|
```
|
|
```bash [.env]
|
|
NUXT_GITHUB_TOKEN='<my-super-token>'
|
|
```
|
|
::
|
|
|
|
::note
|
|
Giving the `event` as argument to `useRuntimeConfig` is optional, but it is recommended to pass it to get the runtime config overwritten by [environment variables](/docs/guide/going-further/runtime-config#environment-variables) at runtime for server routes.
|
|
::
|
|
|
|
### Request Cookies
|
|
|
|
```ts [server/api/cookies.ts]
|
|
export default defineEventHandler((event) => {
|
|
const cookies = parseCookies(event)
|
|
|
|
return { cookies }
|
|
})
|
|
```
|
|
|
|
## Advanced Usage
|
|
|
|
### Nitro Config
|
|
|
|
You can use `nitro` key in `nuxt.config` to directly set [Nitro configuration](https://nitro.unjs.io/config).
|
|
|
|
::warning
|
|
This is an advanced option. Custom config can affect production deployments, as the configuration interface might change over time when Nitro is upgraded in semver-minor versions of Nuxt.
|
|
::
|
|
|
|
```ts [nuxt.config.ts]
|
|
export default defineNuxtConfig({
|
|
// https://nitro.unjs.io/config
|
|
nitro: {}
|
|
})
|
|
```
|
|
|
|
:read-more{to="/docs/guide/concepts/server-engine"}
|
|
|
|
### Nested Router
|
|
|
|
```ts [server/api/hello/[...slug\\].ts]
|
|
import { createRouter, defineEventHandler, useBase } from 'h3'
|
|
|
|
const router = createRouter()
|
|
|
|
router.get('/test', defineEventHandler(() => 'Hello World'))
|
|
|
|
export default useBase('/api/hello', router.handler)
|
|
```
|
|
|
|
### Sending Streams
|
|
|
|
::tip
|
|
This is an experimental feature and is available in all environments.
|
|
::
|
|
|
|
```ts [server/api/foo.get.ts]
|
|
import fs from 'node:fs'
|
|
import { sendStream } from 'h3'
|
|
|
|
export default defineEventHandler((event) => {
|
|
return sendStream(event, fs.createReadStream('/path/to/file'))
|
|
})
|
|
```
|
|
|
|
### Sending Redirect
|
|
|
|
```ts [server/api/foo.get.ts]
|
|
export default defineEventHandler(async (event) => {
|
|
await sendRedirect(event, '/path/redirect/to', 302)
|
|
})
|
|
```
|
|
|
|
### Legacy Handler or Middleware
|
|
|
|
```ts [server/api/legacy.ts]
|
|
export default fromNodeMiddleware((req, res) => {
|
|
res.end('Legacy handler')
|
|
})
|
|
```
|
|
|
|
::important
|
|
Legacy support is possible using [unjs/h3](https://github.com/unjs/h3), but it is advised to avoid legacy handlers as much as you can.
|
|
::
|
|
|
|
```ts [server/middleware/legacy.ts]
|
|
export default fromNodeMiddleware((req, res, next) => {
|
|
console.log('Legacy middleware')
|
|
next()
|
|
})
|
|
```
|
|
|
|
::warning
|
|
Never combine `next()` callback with a legacy middleware that is `async` or returns a `Promise`.
|
|
::
|
|
|
|
### Server Storage
|
|
|
|
Nitro provides a cross-platform [storage layer](https://nitro.unjs.io/guide/storage). In order to configure additional storage mount points, you can use `nitro.storage`, or [server plugins](#server-plugins).
|
|
|
|
**Example of adding a Redis storage:**
|
|
|
|
Using `nitro.storage`:
|
|
|
|
```ts [nuxt.config.ts]
|
|
export default defineNuxtConfig({
|
|
nitro: {
|
|
storage: {
|
|
redis: {
|
|
driver: 'redis',
|
|
/* redis connector options */
|
|
port: 6379, // Redis port
|
|
host: "127.0.0.1", // Redis host
|
|
username: "", // needs Redis >= 6
|
|
password: "",
|
|
db: 0, // Defaults to 0
|
|
tls: {} // tls/ssl
|
|
}
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
Then in your API handler:
|
|
|
|
```ts [server/api/storage/test.ts]
|
|
export default defineEventHandler(async (event) => {
|
|
// List all keys with
|
|
const keys = await useStorage('redis').getKeys()
|
|
|
|
// Set a key with
|
|
await useStorage('redis').setItem('foo', 'bar')
|
|
|
|
// Remove a key with
|
|
await useStorage('redis').removeItem('foo')
|
|
|
|
return {}
|
|
})
|
|
```
|
|
|
|
::read-more{to="https://nitro.unjs.io/guide/storage" target="_blank"}
|
|
Read more about Nitro Storage Layer.
|
|
::
|
|
|
|
Alternatively, you can create a storage mount point using a server plugin and runtime config:
|
|
|
|
::code-group
|
|
```ts [server/plugins/storage.ts]
|
|
import redisDriver from 'unstorage/drivers/redis'
|
|
|
|
export default defineNitroPlugin(() => {
|
|
const storage = useStorage()
|
|
|
|
// Dynamically pass in credentials from runtime configuration, or other sources
|
|
const driver = redisDriver({
|
|
base: 'redis',
|
|
host: useRuntimeConfig().redis.host,
|
|
port: useRuntimeConfig().redis.port,
|
|
/* other redis connector options */
|
|
})
|
|
|
|
// Mount driver
|
|
storage.mount('redis', driver)
|
|
})
|
|
```
|
|
|
|
``` ts [nuxt.config.ts]
|
|
export default defineNuxtConfig({
|
|
runtimeConfig: {
|
|
redis: { // Default values
|
|
host: '',
|
|
port: 0,
|
|
/* other redis connector options */
|
|
}
|
|
}
|
|
})
|
|
```
|
|
::
|