6.1 Nitro架构与API端点


文档摘要

6.1 Nitro 架构与 API 端点 第 3 章把服务器当黑盒用了,本节打开它:Nitro 是什么、server 目录怎么变成接口、一个像样的 REST 端点怎么写。从此你的项目自带后端——第 4 章数据层调的接口,将全部由自己实现。 Nitro 在架构里的位置 图 6-1:Nitro 分层架构 图 6-1:Nitro 分层架构 读图要点:页面代码跑在"应用层",server 目录代码跑在"引擎层"——它们不会进同一个 bundle,前端代码无法 import 服务端模块(构建器直接报错),这从物理上保证了密钥不泄漏。部署抽象层的含义:写一份 server 代码,构建时选 preset 决定它编译成 Node 服务、Serverless 函数还是边缘 worker。

6.1 Nitro 架构与 API 端点

第 3 章把服务器当黑盒用了,本节打开它:Nitro 是什么、server 目录怎么变成接口、一个像样的 REST 端点怎么写。从此你的项目自带后端——第 4 章数据层调的接口,将全部由自己实现。

Nitro 在架构里的位置

图 6-1:Nitro 分层架构

图 6-1:Nitro 分层架构

读图要点:页面代码跑在"应用层",server 目录代码跑在"引擎层"——它们不会进同一个 bundle,前端代码无法 import 服务端模块(构建器直接报错),这从物理上保证了密钥不泄漏。部署抽象层的含义:写一份 server 代码,构建时选 preset 决定它编译成 Node 服务、Serverless 函数还是边缘 worker。

写一个完整的 REST 资源

以商品接口为例,覆盖增删改查。文件路径即接口路径,方法用文件名后缀区分:

// server/api/products/index.get.ts // GET /api/products —— 列表查询,支持分页 export default defineEventHandler(async (event) => { const query = getQuery(event) // 读查询参数 const page = Number(query.page) || 1 const size = Math.min(Number(query.size) || 20, 100) // 上限防滥用 const list = await db.products.findMany({ skip: (page - 1) * size, take: size, }) const total = await db.products.count() return { items: list, total, page, size } // 返回值自动 JSON 序列化 })
// server/api/products/[id].get.ts // GET /api/products/123 —— 详情 export default defineEventHandler(async (event) => { const id = getRouterParam(event, 'id') // 读路径参数 const product = await db.products.findUnique({ where: { id: Number(id) } }) if (!product) { // 资源不存在:抛出带状态码的错误,响应即 404 throw createError({ statusCode: 404, statusMessage: '商品不存在' }) } return product })
// server/api/products/index.post.ts // POST /api/products —— 新建(body 读入 + 基本校验) export default defineEventHandler(async (event) => { const body = await readBody(event) if (!body?.name || typeof body.price !== 'number') { throw createError({ statusCode: 400, statusMessage: 'name 与 price 必填' }) } const created = await db.products.create({ data: body }) setResponseStatus(event, 201) // 创建成功给 201 return created })

方法后缀(get/post/put/delete)同名共存时,同名路径不同方法各归各的文件;没有后缀的文件(如 products.ts)响应所有方法。请求侧工具箱速记:

工具 用途
getQuery / readBody 读查询串 / 读请求体
getRouterParam 读路径动态段
getHeader / setHeader 读写请求响应头
setResponseStatus 设置响应状态码
createError 抛出带状态码的错误
defineEventHandler 一切端点的包装器

类型互通与内部调用

前后端同仓的红利是类型直达。定义共享类型放一个公共位置,两端 import 同一个类型:

// shared/types/product.ts(Nuxt 3.14+ 支持 shared 目录;旧版放 types/ 并配置别名) export interface ProductDTO { id: number name: string priceCents: number }

服务端返回它、客户端 useFetch 泛型标注它,接口改字段时两端同时编译报错——这比任何文档都可靠。服务端内部复用另一个端点的逻辑,用 $fetch 会绕网络;正确姿势是抽普通函数互调,或用 eventHandler 之外的服务端工具 callEvent 类机制直接调用,避免自己请求自己造成的端口占用与死锁(尤其在测试环境)。

⚠️ 常见坑:在端点里读 window 或 Vue 组件上下文。server 代码运行在 Nitro(无浏览器、无 Vue 应用),useRoute、useState 一概不可用;可用的只有 event、runtimeConfig、storage 这些服务端工具。

💡 关键直觉:server 目录的每个文件都是一个"微控制器"——路径是地址、后缀是方法、导出函数是处理体。写接口的心智从"配置路由表"换成"放对文件名"。

补一课:错误响应的完整语义

端点抛错不只是"返回非 200"。createError 携带的状态码与消息有协议语义,值得按 REST 约定用对:400 给参数错误(客户端改了重试有意义)、401 给未登录(引导登录)、403 给无权限(别暴露资源存在与否的细节)、404 给资源不存在、409 给冲突(并发修改)、5xx 留给服务端自己的故障。前端 4.3 节的错误文案分支(按状态码给不同提示)之所以能成立,前提就是服务端把状态码用准了。两端的错误语义是一份契约:服务端乱给 500,客户端的一切优雅降级都无从谈起。

本节要点回顾

  • Nitro 四职:渲染页面、托管 API、静态资源、部署抽象;server 代码与前端代码物理隔离在不同 bundle;
  • 路径即接口:server/api 下文件路径映射 URL,方法后缀区分动词,无后缀全方法响应;
  • 请求工具箱:getQuery、readBody、getRouterParam、createError、setResponseStatus 覆盖日常九成需求;
  • 类型互通:共享 DTO 两端 import,接口变更编译期暴露;
  • 内部复用抽函数:服务端别用 $fetch 请求自己,抽公共函数直调。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U