重新造轮子?HestJS:让 Hono 拥有 NestJS 的优雅

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

作为一个长期使用 GinNestJS 的开发者,我一直对两者有着深深的眷恋:Gin 的简洁高效,NestJS 的优雅架构。但技术世界从不停步,当我看到 denosaurs/bench 的性能测试报告时,两个名字映入眼帘:ElysiaJSHono

数据不会说谎:这两个框架在性能测试中遥遥领先,而且热度都还不错。于是,一场技术探索之旅开始了...

函数式的诱惑与困境

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 的优雅

🎯 核心设计理念

  1. 性能优先:基于 Hono 的高性能内核
  2. 架构清晰:借鉴 NestJS 的模块化设计
  3. 类型安全:深度集成 TypeScript 和 TypeBox
  4. 开发体验:熟悉的装饰器 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 的开发模式
  • ✅ 希望获得更好的性能表现
  • ✅ 需要更轻量级的解决方案
  • ✅ 重视代码的可维护性和团队协作
  • ✅ 希望保持对底层的完全控制权
  • ✅ 期待框架的持续创新和进化

不是为了重新造轮子而造轮子,而是为了解决真实的开发痛点,探索框架设计的新可能。在性能与优雅之间,在封装与灵活之间,找到了最佳的平衡点。


🔗 相关链接

如果这篇文章对你有帮助,别忘了给 HestJS 一个 ⭐️!

让我们一起构建更好的 Node.js 开发体验!


👨‍💻 作者:专注于 Web 性能优化和开发体验提升的工程师

📧 欢迎交流讨论,一起让开发变得更美好!

相关推荐
咔咔一顿操作3 分钟前
常见问题三
前端·javascript·vue.js·前端框架
前端程序媛Ying3 分钟前
点击按钮滚动到底功能vue的v-on:scroll运用
javascript
上单带刀不带妹4 分钟前
Web Worker:解锁浏览器多线程,提升前端性能与体验
前端·js·web worke
pengzhuofan20 分钟前
Web开发系列-第9章 SpringBootWeb登录认证
java·spring boot·后端·web
电商API大数据接口开发Cris20 分钟前
Node.js + TypeScript 开发健壮的淘宝商品 API SDK
前端·数据挖掘·api
还要啥名字23 分钟前
基于elpis下 DSL有感
前端
一只毛驴28 分钟前
谈谈浏览器的DOM事件-从0级到2级
前端·面试
snakeshe101029 分钟前
Java依赖管理演进史:从Classpath地狱到Maven救赎
后端
用户81686947472530 分钟前
封装ajax
前端
pengzhuofan30 分钟前
Web开发系列-第13章 Vue3 + ElementPlus
前端·elementui·vue·web