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

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

作为一个长期使用 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 的优雅。

🎯 核心设计理念

  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 性能优化和开发体验提升的工程师

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

相关推荐
UrbanJazzerati14 分钟前
AutoHotkey v2 零基础快速入门 Part 3 像素亮度检测完全指南
后端
浮链序15 分钟前
Shopify 把 10 亿商品开放给任意 agent 了:我用零依赖 Python 撸了个带预算闸门的代购网关
后端·代码规范
lovingsoft16 分钟前
Chrome Lighthouse 详解
前端·chrome
Qlly26 分钟前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝30 分钟前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦31 分钟前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
花涧鹤43 分钟前
学完循环我才发现,我写的不是死循环,是死机
后端
shmily麻瓜小菜鸡1 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI1 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战