H3 安装与第一个服务
装不上、跑不起来、起来但访问是 404——新手卡在 H3 门口的原因几乎只有这三类。这一节把三道坎一次过掉:装依赖、写 8 行启动代码、在 Node.js / Bun / Deno 三个运行时里跑通并验证。
先确认 Node 版本
H3 v2 是纯 ESM 包,package.json 里明确要求 node >= 20.19.0。先查一下:
## 查看当前 Node 版本,输出应为 v20.19.0 或更高
node -v
## 如果版本偏低,用版本管理器切换(nvm / fnm 均可)
nvm install 20 && nvm use 20
版本不够时,import 语法本身没问题,但 H3 依赖的新 API 会缺失,报错往往出现在运行时而非安装时,很难定位。所以先查版本,再装包。
📌 如果你的项目还在用 CommonJS,最新版 Node 支持
require(esm),因此require("h3")也能拿到模块。但本教程统一用 ESM 写法,避免两种加载方式混用带来的隐性差异。
安装
任选一个包管理器,效果相同:
## npm(Node 自带)
npm install h3
## pnpm
pnpm add h3
## yarn
yarn add h3
国内网络下如果卡在 fetch 阶段,可以临时换源:
## 仅本次安装生效,不改全局配置
npm install h3 --registry=https://registry.npmmirror.com
第一个服务:8 行
新建 server.mjs,内容如下(文件名后缀用 .mjs,直接声明为 ESM):
// 引入应用类:H3 是整本书的主角
// 引入 serve:把应用挂到真实端口上监听,底层由 srvx 适配各运行时
import { H3, serve } from "h3";
// 创建实例并链式注册一条 GET 路由
// 回调的返回值就是响应体,这里返回字符串
const app = new H3().get("/", () => "⚡️ 你好,H3!");
// 监听 3000 端口;port 不传时 srvx 会自己找可用端口
serve(app, { port: 3000 });
在三种运行时里启动
## Node.js:--watch 在文件变更时自动重启
node --watch ./server.mjs
## Bun
bun run --watch server.mjs
## Deno:-A 表示放开全部权限(本地开发够用,生产要收窄)
deno run -A --watch ./server.mjs
启动后终端不会打印多余信息,直接用浏览器或 curl 访问验证:
## 请求根路径,应当原样返回那句话
curl http://localhost:3000/
## 预期输出:⚡️ 你好,H3!
如果访问 /hello 得到 404,那是正常的——目前只注册了 / 一条路由。
这 8 行到底发生了什么
把代码拆成三步看:
第一步,new H3() 建实例。 这个类负责三件事:匹配路由、生成响应、调用中间件与全局钩子。它本身很薄,不预装任何功能。
第二步,.get("/", handler) 注册路由。 这里注册的是「GET 方法 + / 路径」这一组合。同一个路径可以注册不同方法,互不覆盖。
第三步,serve(app, { port }) 开始监听。 serve 来自 srvx——一个跨运行时的通用服务器监听层,把标准的 fetch 语义接到各运行时的网络 API 上。
返回值会自动变成响应
上面例子里返回的是字符串。返回对象也可以,H3 会自动序列化并设置 Content-Type:
// 返回对象 → 自动 JSON 序列化 + content-type: application/json
app.get("/api/ping", (event) => {
return { ok: true, runtime: event.req.headers.get("user-agent") };
});
不用手动 JSON.stringify,也不用自己设头。各类返回值的转换规则(流、Blob、Response、null 等)在 2.5 节展开。
不启监听也能测
写单元测试或者临时验证时,不必占端口。app.request() 直接走一遍内部请求流程,拿到标准 Response:
import { H3, serve } from "h3";
const app = new H3().get("/", () => "⚡️ 你好,H3!");
// 不监听端口,直接构造一次请求并读响应体
const response = await app.request("/");
console.log(await response.text()); // ⚡️ 你好,H3!
这对「写一个 handler 就想验证返回值」的场景非常省事,也是后面写示例时的常用手段。
不装 Node 也能用:CDN 形态
Bun、Deno、Cloudflare Workers 这类运行时支持直接从 URL 引入模块。此时不需要 serve,只要导出 fetch:
// 从 esm.sh 直接引入,无需 npm 安装
import { H3 } from "https://esm.sh/h3";
const app = new H3().get("/", () => "⚡️ 你好,H3!");
// Workers 类运行时约定:导出一个 fetch 处理器即可
export const fetch = app.fetch;
同一份 H3 应用,在自有服务器上是 serve(app),在边缘运行时是 export const fetch = app.fetch——这就是「基于 Web 标准」带来的可移植性。
四个常见报错
| 报错 | 原因 | 处理 |
|---|---|---|
EADDRINUSE |
3000 端口被占 | 换端口,或先结束占用进程 |
Cannot find module 'h3' |
没装包,或在错误目录执行 | 确认 node_modules/h3 存在、命令在项目根执行 |
ERR_REQUIRE_ESM |
用 CommonJS 的 require 加载且 Node 版本过低 |
升级到 20.19+,或改用 import |
| 访问返回 404 | 注册的路径与实际请求不一致 | 检查路由是否写成 /,是否有前导斜杠 |
⚠️ 一个隐蔽的坑:文件名用
.js但项目package.json没有"type": "module"时,import会报语法错误。本教程统一用.mjs后缀规避,等熟悉后再按项目实际配置决定。
延伸阅读
服务跑起来了,接下来该弄清「一个请求进来,H3 按什么顺序处理它」:
- 请求进入后经过哪四个阶段:见 2.1 请求生命周期
- 路由怎么注册、模式怎么归一化:见 2.2 路由
- 返回值如何自动变成响应:见 2.5 发送响应

TRAE-AI编程


更多建议: