Nuxt3 中文课程 《实战全栈开发简书》.

server

server/目录用于在应用程序中注册API和服务器处理程序。

Nuxt会自动扫描这些目录中的文件,以便在应用程序中注册具有HMR支持的API和服务器处理程序。

目录结构
-| server/
---| api/
-----| hello.ts      # /api/hello
---| routes/
-----| bonjour.ts    # /bonjour
---| middleware/
-----| log.ts        # 记录所有请求

每个文件应该导出一个使用defineEventHandler()eventHandler()(别名)定义的默认函数。

处理程序可以直接返回JSON数据、Promise,或使用event.node.res.end()发送响应。

server/api/hello.ts
export default defineEventHandler((event) => {
  return {
    hello: 'world'
  }
})

现在你可以在你的页面和组件中普遍调用这个API:

pages/index.vue
<script setup lang="ts">
const { data } = await useFetch('/api/hello')
</script>

<template>
  <pre>{{ data }}</pre>
</template>

服务器路由

~/server/api目录中的文件会自动在其路由中添加/api前缀。

要添加没有/api前缀的服务器路由,请将它们放入~/server/routes目录中。

示例:

server/routes/hello.ts
export default defineEventHandler(() => 'Hello World!')

在上面的示例中,/hello路由可以通过http://localhost:3000/hello访问。

请注意,当前服务器路由不支持与pages中的动态路由的全部功能。

服务器中间件

Nuxt会自动读取~/server/middleware目录中的任何文件,以创建项目的服务器中间件。

中间件处理程序将在任何其他服务器路由之前在每个请求上运行,以添加或检查标头、记录请求或扩展事件的请求对象。

中间件处理程序不应返回任何内容(也不应关闭或响应请求),只能检查或扩展请求上下文或抛出错误。

示例:

server/middleware/log.ts
export default defineEventHandler((event) => {
  console.log('New request: ' + getRequestURL(event))
})
server/middleware/auth.ts
export default defineEventHandler((event) => {
  event.context.auth = { user: 123 }
})

服务器插件

Nuxt会自动读取~/server/plugins目录中的任何文件,并将它们注册为Nitro插件。这允许扩展Nitro的运行时行为并钩入生命周期事件。

示例:

server/plugins/nitroPlugin.ts
export default defineNitroPlugin((nitroApp) => {
  console.log('Nitro plugin', nitroApp)
})
Read more in Nitro Plugins.

服务器工具

服务器路由由unjs/h3提供支持,并附带一组方便的辅助函数。

Read more in Available H3 Request Helpers.

你可以在~/server/utils目录中自己添加更多的辅助函数。

例如,你可以定义一个自定义处理程序工具,它包装了原始处理程序并在返回最终响应之前执行其他操作。

示例:

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 {
      // 在路由处理程序之前执行某些操作
      const response = await handler(event)
      // 在路由处理程序之后执行某些操作
      return { response }
    } catch (err) {
      // 错误处理
      return { err }
    }
  })

服务器类型

此功能在 Nuxt >= 3.5 版本中可用。

为了提高IDE中对来自'nitro'和'vue'的自动导入的清晰度,你可以添加一个~/server/tsconfig.json文件,内容如下:

server/tsconfig.json
{
  "extends": "../.nuxt/tsconfig.server.json"
}

尽管在目前的版本中这些值不会在类型检查时得到尊重(nuxi typecheck),但你应该在IDE中获得更好的类型提示。

示例

路由参数

服务器路由可以在文件名中使用方括号内的动态参数,如/api/hello/[name].ts,并通过event.context.params访问。

server/api/hello/[name].ts
export default defineEventHandler((event) => {
  const name = getRouterParam(event, 'name')

  return `Hello, ${name}!`
})

现在你可以在/api/hello/nuxt上普遍调用此API,并获得Hello, nuxt!

匹配HTTP方法

处理文件名可以使用.get.post.put.delete等后缀来匹配请求的HTTP方法

server/api/test.get.ts
export default defineEventHandler(() => 'Test get handler')
server/api/test.post.ts
export default defineEventHandler(() => 'Test post handler')

在上面的示例中,使用以下方式获取/test

  • GET方法:返回Test get handler
  • POST方法:返回Test post handler
  • 任何其他方法:返回405错误

你还可以在目录中使用index.[method].ts来以不同的方式组织代码,这对于创建API命名空间非常有用。

export default defineEventHandler((event) => {
  // 处理`api/foo`端点的GET请求
})

捕获所有路由

捕获所有路由对于后备路由处理非常有用。

例如,创建一个名为~/server/api/foo/[...].ts的文件将为所有不匹配任何路由处理程序的请求注册一个捕获所有路由,如/api/foo/bar/baz

server/api/foo/[...].ts
export default defineEventHandler((event) => {
  // event.context.path 获取路由路径:'/api/foo/bar/baz'
  // event.context.params._ 获取路由段:'bar/baz'
  return `Default foo handler`
})

你可以通过使用~/server/api/foo/[...slug].ts来为捕获所有路由设置一个名称,并通过event.context.params.slug访问它。

server/api/foo/[...slug].ts
export default defineEventHandler((event) => {
  // event.context.params.slug 获取路由段:'bar/baz'
  return `Default foo handler`
})

处理请求体

server/api/submit.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  return { body }
})

现在你可以使用以下方式普遍调用此API:

app.vue
<script setup>
async function submit() {
  const { body } = await $fetch('/api/submit', {
    method: 'post',
    body: { test: 123 }
  })
}
</script>
我们在文件名中仅使用submit.post.ts来匹配可以接受请求体的POST方法的请求。在GET请求中使用readBody时,readBody将抛出405 Method Not Allowed HTTP错误。

查询参数

示例查询/api/query?foo=bar&baz=qux

server/api/query.get.ts
export default defineEventHandler((event) => {
  const query = getQuery(event)

  return { a: query.foo, b: query.baz }
})

错误处理

如果没有抛出错误,将返回状态码200 OK

任何未捕获的错误都将返回500 Internal Server Error HTTP错误。

要返回其他错误代码,请使用createError抛出异常:

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'
})

状态码

要返回其他状态码,请使用setResponseStatus实用程序。

例如,返回202 Accepted

server/api/validation/[id].ts
export default defineEventHandler((event) => {
  setResponseStatus(event, 202)
})

运行时配置

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
})
useRuntimeConfig传递event作为参数是可选的,但建议传递它以便在运行时为服务器路由通过环境变量覆盖运行时配置。

请求Cookie

server/api/cookies.ts
export default defineEventHandler((event) => {
  const cookies = parseCookies(event)

  return { cookies }
})

高级用法

Nitro配置

你可以在nuxt.config中使用nitro键直接设置Nitro配置

这是一个高级选项。自定义配置可能会影响生产部署,因为随着Nitro在Nuxt的半个版本中进行升级,配置接口可能会发生变化。
nuxt.config.ts
export default defineNuxtConfig({
  // https://nitro.unjs.io/config
  nitro: {}
})
Read more in Docs > Guide > Concepts > Server Engine.

嵌套路由

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)

发送流

这是一个实验性功能,仅在所有环境中可用。
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'))
})

发送重定向

server/api/foo.get.ts
export default defineEventHandler(async (event) => {
  await sendRedirect(event, '/path/redirect/to', 302)
})

旧版处理程序或中间件

server/api/legacy.ts
export default fromNodeMiddleware((req, res) => {
  res.end('Legacy handler')
})
使用unjs/h3可以支持旧版处理程序,但建议尽量避免使用旧版处理程序。
server/middleware/legacy.ts
export default fromNodeMiddleware((req, res, next) => {
  console.log('Legacy middleware')
  next()
})
永远不要将带有next()回调的旧版中间件与async的中间件或返回Promise的中间件组合使用。

服务器存储

Nitro提供了一个跨平台的存储层。为了配置额外的存储挂载点,你可以使用nitro.storage服务器插件

添加Redis存储的示例:

使用nitro.storage

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    storage: {
      redis: {
        driver: 'redis',
        /* redis连接器选项 */
        port: 6379, // Redis端口
        host: "127.0.0.1", // Redis主机
        username: "", // 需要Redis >= 6
        password: "",
        db: 0, // 默认为0
        tls: {} // tls/ssl
      }
    }
  }
})

然后在你的API处理程序中:

server/api/storage/test.ts
export default defineEventHandler(async (event) => {
  // 使用以下方式列出所有键
  const keys = await useStorage('redis').getKeys()

  // 使用以下方式设置键
  await useStorage('redis').setItem('foo', 'bar')

  // 使用以下方式删除键
  await useStorage('redis').removeItem('foo')

  return {}
})
阅读更多关于Nitro存储层的内容。

或者,你可以使用服务器插件和运行时配置创建存储挂载点:

import redisDriver from 'unstorage/drivers/redis'

export default defineNitroPlugin(() => {
  const storage = useStorage()

  // 动态传递来自运行时配置或其他来源的凭据
  const driver = redisDriver({
      base: 'redis',
      host: useRuntimeConfig().redis.host,
      port: useRuntimeConfig().redis.port,
      /* 其他redis连接器选项 */
    })

  // 挂载驱动程序
  storage.mount('redis', driver)
})