Node.js 框架在 2024 年呈现出多样化的发展态势,其中 Elysia、Hono、Nest 和 Encore 等框架备受关注。本文尝试记录这些框架的优势、劣势以及适用场景,帮助日后技术选型。
框架特性概述
根据轻量级到功能丰富程度,这些框架的排序如下:
这并不意味着轻量级框架不好,而是取决于项目需求。Hono 的轻量级特性使其非常适合部署到 Cloudflare Workers,而 Encore.ts 则内置了许多功能,如自动追踪和本地基础设施。
Encore.ts
仓库地址: https://github.com/encoredev/encore
Encore.ts 是一个开源框架,旨在简化使用 TypeScript 构建健壮且类型安全的后端应用。它内置了丰富的工具,可提升开发体验,在性能方面表现卓越,是本次比较中速度最快的框架。
Encore 具有内置的请求验证功能,使用常规 TypeScript 定义的请求和响应类型可在编译和运行时验证请求。与其他框架不同,实际验证在 Rust 中完成,而非 JavaScript,这使其验证速度极快。
Encore 便于创建和调用服务,从代码角度看,服务就像存储库中的普通文件夹,调用服务端点如同调用普通函数。其神奇之处在于,这些函数调用在底层会转换为实际的 HTTP 调用。部署时,你甚至可以选择将服务部署到不同实例,如 Kubernetes 集群,同时保持所有服务代码在同一存储库中。
Encore 还带有内置的开发仪表板,启动应用后,可通过localhost:9400访问。在这里,你可以像使用 Postman 一样调用端点,每次调用都会生成一个跟踪,用于检查 API 请求、数据库调用和发布 / 订阅消息。本地开发仪表板还包括自动 API 文档和系统的最新架构图。值得一提的是,尽管 Encore 功能丰富,但它没有任何 npm 依赖项。
Hono
仓库地址: https://github.com/honojs/hono
Hono 由 Yusuke Wada 创建,始于 2021 年,旨在解决 Node.js 框架在 Cloudflare Workers 上运行不佳的问题。此后,Hono 增加了对 Node.js、Bun 和 Deno 等多种运行时的支持。
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hono!'))
export default app
Hono 非常小巧,hono/tiny预设版本不到 13KB,非常适合部署到 Cloudflare Workers,并且它也没有 NPM 依赖项,令人印象深刻!
其多运行时支持的卖点很有趣,无论使用何种 JavaScript 运行时,都能运行 Hono。在其存储库中,有适配器概念,可查看针对每个运行时所做的调整。这对框架的采用和用户增长意义重大,但对于单个用户而言,一旦应用部署到云端,可能不会切换运行时。
尽管 Hono 开箱即用很轻量,但它有许多中间件可供安装,以增强应用功能,采用的是 Express 流行的 “即用即装” 方法。对于小型应用,这种方式效果不错,但对于大型应用,维护大量依赖项可能会令人沮丧。
Elysia
Elysia 专为 TypeScript 构建,在 API 处理程序中提供类型安全,这能节省大量时间,无需在代码中到处进行类型检查。
你可以使用t模块(TypeBox 验证库的扩展)指定请求类型。与 Encore 不同,Elysia 的验证在 JavaScript 层进行,这会增加一些性能开销。
import { Elysia, t } from 'elysia'
new Elysia()
.patch("/profile", ({ body }) => body.profile, {
body: t.Object({
id: t.Number(),
profile: t.File({ type: "image" }),
}),
})
.listen(3000);
添加 Swagger 文档只需一行代码,并且 Elysia 原生支持 OpenTelemetry,这意味着无论平台如何,都能轻松监控应用。Elysia 速度很快,但不如 Encore。
Nest.js
Nest.js 与其他框架有所不同。Encore、Elysia 和 Hono 提供创建端点和中间件的简约 API,你可以自由组织业务逻辑,而 Nest.js 更具主见,强制你以特定方式组织代码,它提供模块化架构,将代码组织成不同抽象,如提供者、控制器、模块和中间件。
Nest.js 旨在使大型应用的维护和开发更容易。然而,你是否喜欢 Nest 提供的这种有主见的结构因人而异。对于大规模项目,长期可维护性比速度和简单性更重要时,Nest 可能更具优势。对于只有几个开发人员的小型项目,额外的抽象层在大多数情况下可能过于复杂。与 Hono、Encore 和 Elysia 相比,Nest 的有主见特性也带来了更陡峭的学习曲线。
使用 Nest 时,你可以选择使用 Express 或 Fastify 作为底层 HTTP 服务器框架,Nest 的所有功能都构建在其之上。
性能表现
速度在选择框架时可能不是最重要的因素,但也不容忽视,它会影响应用响应能力和托管成本。
我们对这些框架进行了有无请求模式验证的基准测试,测量单位是每秒请求数。括号中的名称是使用的请求验证库,Encore.ts 内置了请求验证。
在我们的基准测试中,Encore.ts 是所有框架中最快的,其次是 Elysia、Hono、Fastify 和 Express。Nest 在底层使用 Fastify 或 Express,因此其性能与之相当,可能会稍慢一些,因为 Nest 会增加一些开销。
Encore.ts 之所以如此之快,秘诀在于它有一个 Rust 运行时,而 Rust 速度很快!
Encore.ts 实际上由两部分组成:
- 面向用户的 TypeScript 部分,用于定义 API 和基础设施。
- 底层是用 Rust 编写的多线程运行时。
性能提升的关键是将尽可能多的工作从单线程 Node.js 事件循环转移到 Rust 运行时。例如,Rust 运行时处理所有输入 / 输出,如接受传入的 HTTP 请求或从数据库读取数据。一旦请求或数据库查询处理完成,就会交给 Node.js 事件循环。
代码结构
Hono、Elysia 和 Encore 在代码结构组织上没有太多限制,创建 API 和中间件的方式在这些框架之间相当相似。
以下是每个框架的 GET 端点示例,虽然存在一些差异,但在我看来,这些 API 足够相似,不至于成为决定性因素:
Encore.ts
interface Response {
message: string;
}
export const get = api(
{ expose: true, method: "GET", path: "/hello/:name" },
async ({ name }: { name: string }): Promise<Response> => {
const msg = `Hello ${name}!`;
return { message: msg };
},
);
Elysia
import { Elysia, t } from "elysia";
new Elysia()
.get(
"/hello/:name",
({ params }) => {
const msg = `Hello ${params.name}!`;
return { message: msg };
},
{
response: t.Object({
message: t.String(),
}),
},
)
Hono
import { Hono } from "hono";
const app = new Hono();
app.get("/hello/:name", async (c) => {
const msg = `Hello ${c.req.param("name")}!`;
return c.json({ message: msg });
});
Nest.js
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
真正产生影响的是在构建健壮应用时能否依赖类型安全。Encore 和 Elysia 提供类型安全的 API,而 Encore 在处理像发布 / 订阅这样的基础设施时还提供编译时类型安全,并且在调用另一个服务的端点时也有编译时类型安全。如果你之前使用过微服务架构,就会知道这有多重要。
Nest.js 在 API 设计方面脱颖而出,一个 Nest 应用涉及许多概念和抽象,这既是优点也是缺点,取决于个人偏好。查看 Nest 应用时,立即会注意到装饰器的使用,Nest 在依赖注入(将服务注入控制器)等方面严重依赖装饰器。
部署与基础设施
所有这些框架都可以部署到主流云平台(如 Digital Ocean 和 Fly.io)或直接部署到云提供商(如 AWS 或 GCP)。
Encore 提供自动本地基础设施,encore run命令启动应用并启动所有本地基础设施,如数据库和发布 / 订阅主题,无需处理 YAML、Docker Compose 等繁琐配置。构建 Encore 应用时,会得到一个运行时配置,用于提供连接到云端基础设施所需的配置。如果你想快速将 Encore 应用部署到云端且不想自行管理基础设施,可以使用 Encore Cloud,它提供持续集成 / 持续部署(CI/CD)和拉取请求的预览环境,还可以在 AWS 或 GCP 上为你的应用配置所需的所有基础设施,这意味着你的应用不依赖任何第三方服务,你可以完全控制所有基础设施。
Hono 的突出特点是支持多种不同的运行时,因此在部署时有很多选择,部署到 Cloudflare Workers、Netlify 或 AWS Lambda 很容易,且不需要大量配置。
使用 Nest 时,运行nest build命令将 TypeScript 代码编译为 JavaScript,此过程会生成一个包含编译文件的dist目录,然后可以使用 Node.js 运行dist文件夹。
推荐的 Elysia 应用部署方式是使用bun build命令将应用编译为二进制文件,一旦编译完成,运行服务器时无需在机器上安装Bun。
最后
希望通过本文的介绍,你能在下次项目中明确选择合适的框架。每个框架都有其独特的特点和适用场景,根据项目需求和个人偏好做出选择,将有助于提高开发效率和项目质量。.