辛宝的玄酒清谈

我的数字花园 A Solo Place

Vite + Nitro + Hono RPC 实践心得

背景#

之前看 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 的中转服务!开心,又可以不看很多技术新闻了,不相干。

Comments

Comments are disabled for now.