背景
之前看 Nuxt 背后的 nitro 出新的预览版本了,之前我写过一些技术笔记
- 实战类的:《[[使用 Nitro + Serverless 函数实现动态头图 Banner]] 》
- 科普类的《 [[速通-unjs 家族的 Web 框架 Nitro]]》
- 还看过一些技术布道:
- [[state of nitro 2023]]
总的来说,Nuxt 背后的技术团队封装了符合自己规则的 web 后端框架,有低层次 httpServer 浅封装的 h3,相关介绍《 [[unjs家族里的 http 框架 h3]] 》,稍高层次的 nitro ,之后是 Nuxt 全栈。
有一阵没有使用和关注 Nitro,因为生态比较薄弱,虽然写起来类型体操得到的类型提示比较健全,但 Web 框架本身已经没有太多差异了,尤其是这几年发展,符合 Web 标准的 Response 大行其道,差异已经足够小,另一方面 express/hono 已经足够简单, AI 也非常容易学习和编写代码。
编写这篇文章也是因为我看到了下面这个帖子,有一些在转推这个说法说在 Vite里边使用 Nitro非常容易。

看到这个消息我试验了试验,现在搞明白了,他说的所有意思,试了试效果很不错,所以就有了这篇文章。
基础概念
Nitro 使用
Nitro 是一个Web 框架、Web服务器,Nitro 是一个Web 框架、Web服务器,通过它可以很轻松的编写API项目,它可以单独使用,这次它可以和 Vite结合使用。
对于普通的 Vite 项目,比如 Vite + React,浏览器端其实没有任何后端框架,只是一个 SPA 单页面应用。我们可以直接在 Vite 项目中安装 Nitro 这个依赖。
安装依赖后,只需要在 Vite 的配置文件里引入 Nitro。这样,当前 Vite 项目就自动集成了一个后端服务,可以在后端编写数据接口,然后在前端调用这些接口。

从使用来看这是一个 Vite 插件,并不一定要用它提供的 defineAPI 服务。它其实是一个 Node 服务的入口,可以启动任意的 Node 服务。
官网也提到可以结合 HONO 框架。HONO 我很熟悉,所以做了一个结合:在入口文件里引入并导出 HONO,同时将预设环境设置为 Node。这样后端可以写熟悉的后端代码,前端继续用前端代码,Nitro 只是作为胶水层把两者结合起来。
这样做的好处是,前端和后端可以写在同一个项目里,结合得非常松散。进一步来说,HONO 推荐使用 RPC(远程过程调用)。你可以在 HONO 中通过特定方式导出 app 及其类型,然后前端直接使用,获得完整一致的全栈开发体验。
Hono RPC
RPC 功能允许在服务器和客户端之间共享 API 规范。
比如后端用 Zod 定义接口的入参和返回结果,把类型导出后,前端直接导入即可。这样所有方法和参数都能被类型校验,前端明确知道参数怎么传、返回结果是什么类型,写起来非常自然。这就实现了一站式开发体验,而且不需要使用 Next.js 或 Nuxt.js。这两个框架我评价不高,因为它们为了实现这些能力,牺牲了很多,变成了黑盒。而 Astro 在动态网页上做起来非常吃力。
通过这种方式,就变成了一个全栈式框架,并且支持渐进式学习。接下来我会进一步从技术角度讲讲具体如何实现,以及体验上的优势。
代码使用
刚才讲了 Nitro 的基础概念,现在补充一下。Nitro 是一个Web框架,它的生态一般,但和 Vite 的结合体验非常好。
对于普通的 Vite 项目,比如 Vite + React,浏览器端其实没有任何后端框架,只是一个 SPA 单页面应用。我们可以直接在 Vite 项目中安装 Nitro 这个依赖。
npm i nitro
安装依赖后,只需要在 Vite 的配置文件里引入 Nitro,这样当前 Vite 项目就自动集成了一个后端服务,可以在后端编写数据接口,然后在前端调用这些接口。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { nitro } from "nitro/vite";
export default defineConfig({
plugins: [
nitro(),
react(),
],
nitro: {
serverDir: './'
},
})
Nitro 更像是一个插件,并不一定要用它提供的 define API 服务。它其实是一个 Node 服务的入口。有了 Nitro,相当于可以启动任意的 Node 服务。官网也提到可以结合 HONO 框架。HONO 我很熟悉,所以做了一个结合:在入口文件里引入并导出 HONO,同时将预设环境设置为 Node。这样后端可以写熟悉的后端代码,前端继续用前端代码,Nitro 只是作为胶水层把两者结合起来。
import { Hono } from "hono";
import authRoutes from "./server/auth";
// 固定写法:链式调用 routes
export const routes = app.route("/auth", authRoutes);
// 固定写法:导出 AppType
export type AppType = typeof routes;
export default app;
这样做的好处是,前端和后端可以写在同一个项目里,结合得非常松散。进一步来说,HONO 推荐使用 RPC(远程过程调用)。你可以在 HONO 中通过特定方式导出 app 及其类型,然后前端直接使用,获得完整一致的全栈开发体验。
比如后端用 Zod 定义接口的入参和返回结果,把类型导出后,前端直接导入即可。这样所有方法和参数都能被类型校验,前端明确知道参数怎么传、返回结果是什么类型,写起来非常自然。这就实现了一站式开发体验,而且不用学习 Next.js 或 Nuxt.js。这两个框架我评价不高,因为它们为了实现这些能力,牺牲了很多,变成了黑盒。
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
// Zod schemas
const registerSchema = z.object({
email: z.string().email(),
username: z.string().min(3).max(20),
password: z.string()
.min(8, "密码至少8个字符")
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, "密码必须包含大小写字母和数字"),
})
const app = new Hono()
// 注册接口
.post('/register',
zValidator('json', registerSchema),
async (c) => {
try {
const { email, username, password } = c.req.valid('json')
return c.json({
status: true,
message: '注册成功',
data: {
user,
token,
}
})
} catch (error) {
console.error('注册错误:', error)
return c.json({ status: false, message: '注册失败' }, 500)
}
})
export default app
Vite 启动时候会启动这个服务,默认通过 3000 端口访问。接下来,我们导出的 app type 会在前端使用。就像截图所示,需要引入 Hono Slant 导出的 hc。定义 baseURL 后,就可以获得这个 RPC 客户端。
有了这个 client 后,传递 app type 泛型,就能获得对应的类型提示。如下截图所示,输入 client,通过点对象操作就能像对象一样简单地调用 API 请求。而且返回值 data 也有类型提示,这意味着请求数据时参数可以得到校验和类型提示,数据返回后也能获得类型提示。这让代码逻辑和代码生成变得非常简单。
import { hc } from "hono/client";
import type { AppType } from "../../server";
// 创建 RPC 客户端
const client = hc<AppType>("http://localhost:3000");
const response = await client.auth.register.$post({
json: {
email: values.email,
username: values.username,
password: values.password,
},
});
const data = await response.json();
if (data.status && data.data) {}
有了这样的逻辑,我们很容易就能写出一个全站一体化的应用。在后端定义具体逻辑并导出对应类型,前端应用类型后可以快速发起请求,并获得相应的类型提示。有了 AI 的加持,代码可以写得更快、跑得更快。
展望
Web 全栈拼图完整了:
- Astro 开发内容展示类网站
- Vite + Nitro + Hono RPC 渐进式开发一站式全栈应用
这就搞一个 API Key 的中转服务!开心,又可以不看很多技术新闻了,不相干。