简体中文
快速开始
本文带你创建一个 hazejs 项目并运行起来,然后亲手添加第一个路由和控制器。
版本约定
@hazejs/* 各包与 create-hazejs 共用一个版本号,遵循语义化版本。升级时请一起升级,并查看更新日志了解变化。
环境要求
- Node.js 22.12 及以上(Vite 8 的要求)。
- 推荐使用 pnpm,npm 和 yarn 同样可用。
创建项目
sh
pnpm create hazejs my-appsh
npm create hazejs@latest my-appsh
yarn create hazejs my-app不带 -t 参数时会交互式地让你选择模板,也可以直接指定:
sh
pnpm create hazejs my-app -t api # 纯后端 API
pnpm create hazejs my-app -t vue # Vue 3 全栈
pnpm create hazejs my-app -t react # React 19 全栈TIP
使用 npm 时,参数需要用 -- 与包名隔开:npm create hazejs@latest my-app -- -t vue。
| 模板 | 内容 |
|---|---|
api | 纯后端服务,代码直接位于项目根目录(app/、config/、route/) |
vue | Vue 3 + hazejs:同一个仓库、同一个端口、一次构建,后端代码位于 server/ |
react | 同样的全栈结构,前端换成 React 19 |
module | 可发布的模块包(路由、中间件、配置与迁移),供其它项目在 config/modules.ts 中启用 |
在非交互环境中(CI、没有 TTY)默认使用 api 模板。目标目录非空时需要加 --force(会先清空该目录);加 --auto-import 可开启自动导入,控制器、服务与框架 API 无需手写 import。
脚手架会:
- 复制模板,并根据目录名设置
package.json的name; - 在
.env中写入一个随机生成的APP_KEY,用于签名 Cookie、加密 Session,同时也是 JWT 的默认密钥; - 生成
pnpm-workspace.yaml,声明依赖安装脚本的处理方式(pnpm 11 要求显式声明);如果项目创建在已有的 pnpm workspace 内则跳过。
脚手架不会自动安装依赖,接下来手动安装:
sh
cd my-app
pnpm install模板里有什么
api 模板是一个完整可运行的示例,而不是空壳,包括:
- 带请求校验的
GET /api/hello与POST /api/hello; - 基于 SQLite 和
User模型的users资源接口,输出由 API 资源(app/resource/UserResource.ts)控制,邮箱只对本人和管理员可见; - JWT 注册、登录和
/api/auth/me,以及“只能修改自己资料”的授权策略; /api/auth下的账号接口:个人访问令牌(/tokens)、邮箱验证、找回密码,以及 GitHub 登录(在.env中填写GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET后启用,配置在config/oauth.ts,使用@hazejs/oauth);- 流式 AI 对话接口
POST /api/chat(config/ai.ts); - 事件、监听器、队列任务、邮件和定时任务各一个示例;
- 一个通知(
WelcomeNotification,经邮件和广播发送),以及每个用户的私有广播频道(app/channel/user.ts); lang/en与lang/zh-CN两套翻译和config/i18n.ts,欢迎邮件、校验错误与框架消息都按请求的语言输出(见多语言);- 一个示例命令
haze inspire(app/command/Inspire.ts); - 位于
/api/ws的 WebSocket 路由; - 覆盖以上功能的测试。
全栈模板在此基础上增加了 src/ 下的 Vue / React 前端(含一个 AI 对话组件)和一个 Socket.IO 命名空间。每个目录的用途见目录结构。
运行项目
模板默认使用 SQLite(data/app.db)。首次运行前先执行一次迁移建表,再启动开发服务器:
sh
pnpm db:migrate
pnpm devapi 模板监听 http://localhost:3000:
sh
curl localhost:3000/api/hello
# {"code":0,"message":"ok","data":{"app":"my-app","message":"Hello, hazejs!"}}全栈项目则由 Vite 启动,使用 Vite 的端口(默认 http://localhost:5173),页面和 /api/** 共用这一个端口,详见 Vite 全栈。
还有几个页面无需任何配置即可使用:开发环境下,/__haze 是开发面板(请求、查询、任务、邮件、日志),/docs 是根据路由生成的 OpenAPI 文档;/health 在任何模式下都会响应健康检查。全栈项目中 Vite 开发服务器只把 /api/** 与 /__haze 交给后端,需要后两者时,把 OpenAPI 的地址(config/openapi.ts 的 path 与 docsPath)和健康检查的地址(config/health.ts 的 path)移到 /api 下。
修改任意控制器、服务、路由或配置文件,下一个请求就会用上新代码。进程不会重启,终端只会打印一行 reloaded。
第一个路由和控制器
先生成一个服务和一个控制器:
sh
npx haze make:service Post # app/service/PostService.ts
npx haze make:controller Post # app/controller/PostController.ts服务负责业务逻辑,不感知 HTTP:
ts
// app/service/PostService.ts
export class PostService {
private posts = [
{ id: 1, title: 'Hello hazejs' },
{ id: 2, title: 'Second post' },
]
all() {
return this.posts
}
find(id: number) {
return this.posts.find(post => post.id === id)
}
}控制器接收请求,通过 inject() 调用服务,直接返回数据即可:
ts
// app/controller/PostController.ts
import type { HttpContext } from '@hazejs/core'
import { inject, NotFoundException } from '@hazejs/core'
import { PostService } from '~/app/service/PostService'
export class PostController {
private posts = inject(PostService)
index() {
return this.posts.all()
}
show(ctx: HttpContext) {
const post = this.posts.find(Number(ctx.param('id')))
if (!post)
throw new NotFoundException(`Post ${ctx.param('id')} not found`)
return post
}
}~ 是指向后端源码目录的别名,无论文件放在哪一层,导入路径都保持一致。路由不会从控制器自动推导,需要在 route/api.ts 已有的 /api 分组里注册:
ts
// route/api.ts
import { PostController } from '~/app/controller/PostController'
export default defineRoutes((r) => {
r.group({ prefix: '/api', as: 'api.' }, (r) => {
// ...已有的路由
r.get('/posts', [PostController, 'index']).name('posts.index')
r.get('/posts/:id', [PostController, 'show']).name('posts.show')
})
})保存后试一下:
sh
curl localhost:3000/api/posts/1
# {"code":0,"message":"ok","data":{"id":1,"title":"Hello hazejs"}}
curl localhost:3000/api/posts/9
# 404,由异常处理器按同样的响应结构输出运行 pnpm routes 可以在路由表中看到新增的两条。由于路由带了名字,生成类型之后(开发服务器会自动生成),router.url('api.posts.show', { id: 1 }) 的路由名和参数都会经过类型检查。
加一个测试
sh
npx haze make:test post # tests/post.test.tsts
// tests/post.test.ts
import { useTestApp } from '@hazejs/testing'
import { describe, expect, it } from 'vitest'
const t = useTestApp()
describe('posts', () => {
it('shows a post', async () => {
const res = await t.get('/api/posts/1')
expect(res.status).toBe(200)
expect(res.body.data).toEqual({ id: 1, title: 'Hello hazejs' })
})
})sh
pnpm test测试在进程内启动真实的应用,不监听端口。详见测试。
默认脚本
所有模板都自带以下脚本:
| 脚本 | 命令 | 作用 |
|---|---|---|
dev | haze dev | 开发服务器,原地热替换 |
build | haze build | 构建生产产物(dist/) |
start | haze start | 运行生产产物 |
preview | haze preview | 按构建时的部署预设在本地运行产物 |
queue | haze queue:work | 消费队列任务 |
schedule | haze schedule:work | 常驻执行定时任务(每分钟检查) |
test | haze test | 运行 Vitest |
routes | haze route:list | 打印路由表 |
typecheck | tsc --noEmit | 类型检查(全栈模板为 vue-tsc --build / tsc --build) |
typegen | haze typegen | 生成配置键、路由、授权能力与翻译键的类型,以及类型安全客户端使用的路由表 |
db:generate | haze db:generate | 根据表结构变化生成迁移 |
db:migrate | haze db:migrate | 执行尚未运行的迁移 |
db:seed | haze db:seed | 执行数据填充 |
额外的参数会原样传给命令,例如 pnpm db:generate --name create_posts。所有命令的说明见命令行参考。
AI 编码助手
hazejs 提供了 Agent Skills,让 Claude Code、Cursor、Codex 等支持 skill 的编码助手掌握框架的约定与 API。用 skills 命令行安装到项目中:
sh
npx skills add penna-org/haze命令会列出可用的 skill,并询问要安装哪些、装给哪些助手:
| Skill | 用途 |
|---|---|
hazejs | 用 hazejs 开发应用:路由、校验、数据库、认证、队列、实时通信、AI、测试、部署等 |
hazejs-migration | 把 Fastify、Express、Koa、Hono 或 NestJS 后端迁移到 hazejs,同时保持原有接口契约 |
hazejs-contributing | 开发框架本身(penna-org/haze 仓库) |
也可以直接指定:--skill hazejs --agent claude-code;加 -g 安装到用户级而不是当前项目;升级 hazejs 后运行 npx skills update 更新。配合开发者工具的 MCP 端点,助手还能直接查看正在运行的应用。
下一步
- 目录结构:每个目录的用途
- 配置:
config/*.ts、.env与haze.config.ts - 路由与控制器:深入 HTTP 层
- 数据库:表结构、迁移与模型
- API 资源与 OpenAPI 文档:控制接口输出、生成接口文档,并在前端使用类型安全的客户端
- 认证、API 令牌、两步验证与通行密钥和第三方登录:模板自带的账号功能
- 通知与广播:通过邮件、群机器人和实时频道触达用户
- 多语言:翻译文件与按请求切换语言
- 自定义命令:编写自己的
haze命令,以及 REPL - 可观测性:健康检查、OpenTelemetry 与开发面板
- 测试:测试客户端与各种 fake
- 部署:构建到 Node、Docker、Serverless 或 Edge