前言:一次技术选型的"踩坑"之旅

作为一个长期使用 Gin 和 NestJS 的开发者,我一直对两者有着深深的眷恋:Gin 的简洁高效,NestJS 的优雅架构。但技术世界从不停步,当我看到 denosaurs/bench 的性能测试报告时,两个名字映入眼帘:ElysiaJS 和 Hono。
数据不会说谎:这两个框架在性能测试中遥遥领先,而且热度都还不错。于是,一场技术探索之旅开始了...
函数式的诱惑与困境
ElysiaJS 和 Hono 的第一印象都不错:
typescript
// 简洁的 API 设计
app.get('/users', c => {
return c.json({ users: [] });
});
// 类型安全的路由定义
app.post('/users', c => {
const body = c.req.body();
return c.json({ success: true });
});
函数式确实上手简单,写起来很爽。但是,当你和团队一起开发,当项目规模逐渐扩大,问题就来了:
🚨 问题一:代码风格的混乱
typescript
// 开发者 A 的写法
app.get('/users', async c => {
const users = await getUsersFromDB();
return c.json(users);
});
// 开发者 B 的写法
const handleGetUsers = async context => {
try {
const result = await userService.findAll();
return context.json({ data: result, status: 'success' });
} catch (error) {
return context.json({ error: error.message }, 500);
}
};
app.get('/users', handleGetUsers);
// 开发者 C 的写法
app.get('/users', c =>
userRepository
.findAll()
.then(users => c.json({ users }))
.catch(err => c.json({ error: err }, 500)),
);
三个人,三种写法,维护起来简直是噩梦。没有统一的架构约束,代码腐化的速度令人咋舌。
🚨 问题二:扩展性的瓶颈
当你需要添加全局中间件、统一的错误处理、依赖注入时,函数式的局限性就暴露无遗:
typescript
// 想要复用逻辑?手动传参数
app.get('/users', c =>
handleWithAuth(c, handleWithValidation(c, getUserHandler)),
);
// 想要依赖注入?手动管理实例
const userService = new UserService(new UserRepository(db));
app.get('/users', c => getUserHandler(c, userService));
这时候,我开始怀念 NestJS 的优雅了。
探索之路:从 EestJS 到 HestJS
第一次尝试:EestJS(基于 ElysiaJS)
我首先尝试基于 ElysiaJS 构建了 EestJS,用装饰器和依赖注入包装函数式的核心:
typescript
@Controller('/users')
export class UserController {
constructor(private userService: UserService) {}
@Get('/')
async getUsers() {
return this.userService.findAll();
}
}
看起来不错对吧?但当我深入 ElysiaJS 的源码时,我震惊了...
😱 ElysiaJS 源码的"惊喜"
为了极致的性能优化,ElysiaJS 使用了 AOT(提前编译)进行字符串硬编码:
typescript
// 截取 ElysiaJS 源码 https://github.com/elysiajs/elysia/blob/main/src/compose.ts
const createReport = ({
context = 'c',
trace = [],
addFn
}: {
context?: string
trace?: (TraceHandler | HookContainer<TraceHandler>)[]
addFn(string: string): void
}) => {
if (!trace.length)
return () => {
return {
resolveChild() {
return () => {}
},
resolve() {}
}
}
for (let i = 0; i < trace.length; i++)
addFn(
`let report${i},reportChild${i},reportErr${i},reportErrChild${i};` +
`let trace${i}=${context}[ELYSIA_TRACE]?.[${i}]??trace[${i}](${context});\n`
)
return (
event: TraceEvent,
单个文件2529行。。。更让我担心的是,GitHub 上的 Contributors 寥寥无几,几乎都是作者一个人在维护。这种架构让其他开发者很难参与贡献,长期发展令人担忧。
HestJS 诞生:最佳实践的结合
基于 Hono 的坚实基础,我开始构建 HestJS,目标很明确:保持 Hono 的性能,拥有 NestJS 的优雅。
🎯 核心设计理念
- 性能优先:基于 Hono 的高性能内核
- 架构清晰:借鉴 NestJS 的模块化设计
- 类型安全:深度集成 TypeScript 和 TypeBox
- 开发体验:熟悉的装饰器 API
✨ 让我们看看效果
typescript
// 🚀 应用启动
@Module({
controllers: [UserController],
providers: [UserService],
})
export class AppModule {}
const app = await HestFactory.create(AppModule);
typescript
// 📝 优雅的控制器
@Controller('/users')
export class UserController {
constructor(private userService: UserService) {}
@Get('/')
async findAll() {
return this.userService.findAll();
}
@Post('/')
async create(@Body(CreateUserDto) dto: CreateUserDto) {
return this.userService.create(dto);
}
}
typescript
// ✅ 强大的验证系统
export class CreateUserDto {
@IsString({ minLength: 2, maxLength: 50 })
name!: string;
@IsEmail()
email!: string;
// 🎉 内置的便捷验证器
@CommonValidators.ChinesePhone()
phoneNumber!: string;
// 🔧 自定义 TypeBox Schema
@Custom(SchemaFactory.chineseIdCard())
idCard!: string;
}
🛠️ 核心特性展示
1. 模块化架构
告别函数式的混乱,拥抱清晰的模块结构:
arduino
src/
├── modules/
│ ├── users/
│ │ ├── users.controller.ts
│ │ ├── users.service.ts
│ │ ├── users.module.ts
│ │ └── dto/
│ └── auth/
├── common/
│ ├── filters/
│ └── interceptors/
└── config/
2. TypeBox 深度集成
不仅仅是装饰器语法糖,还有强大的自定义能力:
typescript
// 🚀 便捷的 Schema 工厂
@Custom(SchemaFactory.enum(['admin', 'user', 'guest']))
role!: string
@Custom(SchemaFactory.range(18, 65))
age!: number
@Custom(SchemaFactory.pagination())
query!: PaginationDto
// 🔥 复杂的条件验证
@Custom(
Type.Intersect([
Type.Object({ type: Type.Literal('premium') }),
Type.Object({ features: Type.Array(Type.String(), { minItems: 1 }) })
])
)
subscription!: { type: 'premium'; features: string[] }
3. 全局中间件生态
typescript
// 🛡️ 全局异常过滤器
app.useGlobalFilters(new HttpExceptionFilter());
// 📊 响应格式化拦截器
app.useGlobalInterceptors(new ResponseInterceptor());
// ✅ 验证拦截器
app.useGlobalInterceptors(new ValidationInterceptor());
架构哲学:适配器模式的智慧
🔧 不是封装,而是适配
很多人可能会问:你那么喜欢NestJS,为什么不直接在 NestJS 基础上实现适配器?为什么要重新构建框架?
答案很简单:我想要更强的灵活度和控制权。
HestJS 的核心设计哲学是适配器模式,而不是过度封装。看看我们是如何保持原生 Hono 的能力的:
typescript
async function bootstrap() {
const app = await HestFactory.create(AppModule);
// 🎯 直接访问原生 Hono 实例
app.hono().use(cors()); // 使用 Hono 的 CORS 中间件
app.hono().use('*', log()); // 使用 Hono 的日志中间件
// 🛡️ HestJS 的增强功能
app.useGlobalInterceptors(new ValidationInterceptor());
app.useGlobalInterceptors(new ResponseInterceptor());
app.useGlobalFilters(new HttpExceptionFilter());
// 🚀 完全的底层控制权
Bun.serve({
port: 3002,
fetch: app.hono().fetch,
reusePort: true,
});
}
🌟 适配器的威力
这种设计带来了什么好处?
1. 零性能损失
typescript
// 你可以随时绕过 HestJS,直接使用 Hono
app.hono().get('/raw-performance', (c) => {
// 直接的 Hono 处理器,零额外开销
return c.json({ message: 'Raw speed!' })
})
2. 渐进式迁移
typescript
// 现有的 Hono 项目可以逐步迁移
const existingHonoApp = new Hono()
existingHonoApp.get('/legacy', handler)
// 将现有路由挂载到 HestJS
app.hono().route('/api/v1', existingHonoApp)
3. 插件生态兼容
typescript
// 所有 Hono 的中间件都能直接使用
import { jwt } from 'hono/jwt'
import { compress } from 'hono/compress'
app.hono().use(jwt({ secret: 'secret' }))
app.hono().use(compress())
🔮 未来的可能性
更激动人心的是,适配器模式为我们打开了无限可能:
typescript
// 未来可能的多框架支持
const app = await HestFactory.create(AppModule, {
adapter: 'hono' // 当前默认
// adapter: 'fastify' // 未来可能
// adapter: 'express' // 未来可能
// adapter: 'elysia' // 未来可能
})
想象一下:同样的业务代码,可以在不同的底层框架上运行,根据场景选择最适合的性能特性!
🤔 为什么不基于 NestJS?
这个问题我也思考过很久。NestJS 确实很优秀,但它有几个让我犹豫的地方:
1. 历史包袱重
typescript
// NestJS 的复杂依赖链
@nestjs/core → @nestjs/common → reflect-metadata → rxjs → ...
// 光是启动就要加载大量模块
2. 定制化困难
typescript
// 在 NestJS 中,很多底层行为是固化的
// 想要定制路由解析、参数绑定等,需要深度 hack,需要做的黑魔法太多,有这时间我都搓一个NestJS了
3. 性能天花板
typescript
// NestJS 基于 Express/Fastify
// 无论如何优化,都受限于底层框架的性能上限
4. 创新空间受限
typescript
// 在现有框架基础上创新,总是要考虑向后兼容
// 很多好的想法因为 "breaking change" 而无法实现
🎯 HestJS 的自由度
重新构建让我们拥有了完全的自由:
1. 架构创新
typescript
// 我们可以实验性地支持新特性
@Controller('/api/v2')
export class StreamingController {
// 假设未来支持 HTTP/3 或 WebAssembly
@Stream('/events')
async streamEvents() {
return new EventStream()
}
}
2. 性能极致优化
typescript
// 可以针对特定场景做深度优化
export class HyperPerformanceModule {
// 假设针对游戏服务器的特殊优化
@WebSocket('/game')
gameHandler() { /* ... */ }
}
3. 未来技术栈的兼容
typescript
// 当新的运行时出现时,我们可以快速适配
// Deno 2.0? Node.js 新特性? 都不是问题
💡 开发哲学的思考
选择重新构建而不是基于现有框架,反映了一种开发哲学:
不是所有的轮子都值得重造,但当现有的轮子无法带你到想去的地方时,造一个新的可能是唯一选择。
HestJS 不是为了证明"我能造轮子",而是为了探索"后端框架还能怎样更好"。
开发体验的提升
🔥 热重载开发
bash
# 秒级启动
bun run dev
# ✅ 应用启动完成 - 68ms
📦 一键部署
bash
# 独立可执行文件
bun run build:binary
./dist/hest-demo
# 或者容器化部署
FROM oven/bun:alpine
COPY dist/ /app/
CMD ["/app/index.js"]
实际项目效果
经过实际项目使用,效果超出预期:
📈 开发效率提升
- 上手时间减半:熟悉的 NestJS 风格
- AI更喜欢的OOP:经过测试我发现 AI 更喜欢OOP,也更容易写出来可维护的代码,AI会默认认为这个是NestJS
🚀 性能表现
- 内存占用:相比 NestJS 减少 70%
- 冷启动时间:< 100ms
- 编译速度:< 100ms(包含所有模块)
社区与生态
HestJS 目前已经开源,我正在建设:
- 📚 完善的文档:从入门到高级用法
- 🔧 CLI 工具:快速生成项目模板
- 🧩 插件生态:数据库、认证、缓存等
- 👥 活跃社区:欢迎贡献和反馈
结语:为什么选择 HestJS?
如果你:
- ✅ 熟悉并喜爱 NestJS 的开发模式
- ✅ 希望获得更好的性能表现
- ✅ 需要更轻量级的解决方案
- ✅ 重视代码的可维护性和团队协作
- ✅ 希望保持对底层的完全控制权
- ✅ 期待框架的持续创新和进化
不是为了重新造轮子而造轮子,而是为了解决真实的开发痛点,探索框架设计的新可能。在性能与优雅之间,在封装与灵活之间,找到了最佳的平衡点。
🔗 相关链接
- GitHub : github.com/aqz236/hest
- 文档: 正在完善中...
- 演示项目: 随仓库提供完整示例
如果这篇文章对你有帮助,别忘了给 HestJS 一个 ⭐️!
让我们一起构建更好的 Node.js 开发体验!
👨💻 作者:专注于 Web 性能优化和开发体验提升的工程师
📧 欢迎交流讨论,一起让开发变得更美好!