透過 100 多個技巧學習 Nuxt!

伺服器

server/ 目錄用於註冊應用程式的 API 和伺服器處理器。

Nuxt 會自動掃描這些目錄中的檔案,以註冊 API 和伺服器處理器,並支援熱模組替換 (HMR)。

目錄結構
-| server/
---| api/
-----| hello.ts      # /api/hello
---| routes/
-----| bonjour.ts    # /bonjour
---| middleware/
-----| log.ts        # log all requests

每個檔案都應匯出一個使用 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 路由將可透過 https://127.0.0.1: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)
})
Nitro 外掛 中閱讀更多內容。

伺服器公用程式

伺服器路由由 unjs/h3 提供支援,其中包含一組方便的輔助程式。

可用的 H3 請求輔助程式 中閱讀更多內容。

您可以在 ~/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 {
      // 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 }
    }
  })

伺服器類型

此功能可從 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}!`
})
或者,使用 getValidatedRouterParams 以及 Zod 等結構驗證器,以進行執行階段和類型安全。

現在,您可以在 /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) => {
  // handle GET requests for the `api/foo` endpoint
})

萬用路由 (Catch-all Route)

萬用路由對於處理後備路由很有幫助。

例如,建立一個名為 ~/server/api/foo/[...].ts 的檔案,將會為所有不符合任何路由處理程式的請求註冊一個萬用路由,例如 /api/foo/bar/baz

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

您可以使用 ~/server/api/foo/[...slug].ts 為萬用路由設定一個名稱,並透過 event.context.params.slug 來存取它。

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 處理

server/api/submit.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  return { body }
})
或者,使用 readValidatedBody 和 schema 驗證器(例如 Zod)來實現執行時和類型安全。

您現在可以使用以下方式通用地呼叫此 API:

app.vue
<script setup lang="ts">
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 }
})
或者,使用 getValidatedQuery 和 schema 驗證器(例如 Zod)來實現執行時和類型安全。

錯誤處理

如果沒有拋出任何錯誤,將會回傳 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
})
event 作為參數傳遞給 useRuntimeConfig 是選用的,但建議傳遞它,以便在伺服器路由的執行階段,取得被 環境變數 覆寫的執行階段設定。

請求 Cookies

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

  return { cookies }
})

轉發 Context & Headers

預設情況下,當在伺服器路由中發出 fetch 請求時,不會轉發來自傳入請求的標頭或請求 context。您可以使用 event.$fetch 在伺服器路由中發出 fetch 請求時轉發請求 context 和標頭。

server/api/forward.ts
export default defineEventHandler((event) => {
  return event.$fetch('/api/forwarded')
})
不打算轉發的標頭將不會包含在請求中。這些標頭包括例如:transfer-encodingconnectionkeep-aliveupgradeexpecthostaccept

回應後等待 Promise

在處理伺服器請求時,您可能需要執行不應阻止客戶端回應的非同步任務(例如,快取和記錄)。您可以使用 event.waitUntil 在背景中等待 Promise,而不會延遲回應。

event.waitUntil 方法接受一個 Promise,該 Promise 將在處理程式終止之前等待,確保即使伺服器在傳送回應後立即終止處理程式,任務也會完成。這與執行階段供應商整合,以利用其原生功能來處理回應傳送後的非同步操作。

server/api/background-task.ts
const timeConsumingBackgroundTask = async () => {
  await new Promise((resolve) => setTimeout(resolve, 1000))
};

export default eventHandler((event) => {
  // schedule a background task without blocking the response
  event.waitUntil(timeConsumingBackgroundTask())

  // immediately send the response to the client
  return 'done'
});

進階用法

Nitro 設定

您可以使用 nuxt.config 中的 nitro 鍵直接設定 Nitro 設定

這是一個進階選項。自訂設定可能會影響生產環境部署,因為當在 Nuxt 的 semver-minor 版本中升級 Nitro 時,設定介面可能會隨著時間而變更。
nuxt.config.ts
export default defineNuxtConfig({
  // https://nitro.unjs.io/config
  nitro: {}
})
請在 文件 > 指南 > 概念 > 伺服器引擎 中閱讀更多內容。

巢狀路由

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

然後在您的 API 處理程式中:

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 {}
})
請閱讀更多關於 Nitro 儲存層的資訊。

或者,您可以使用伺服器外掛程式和執行階段設定來建立儲存掛載點

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