五個框架的定位
Node.js 生態的 framework 分成兩個維度:大小(minimal vs batteries-included)和目標 runtime(Node.js only vs multi-runtime)。
| Framework | 定位 | Runtime | 型別安全 |
|---|---|---|---|
| Express | minimal,最成熟 | Node.js | TypeScript 可用,但不 native |
| Fastify | minimal but fast,schema-driven | Node.js | TypeScript 原生 |
| Hono | ultra-minimal,跨 runtime | Node / Bun / CF Workers / Deno | TypeScript 原生,RPC style |
| NestJS | batteries-included,Angular 架構 | Node.js | TypeScript 原生 |
| Elysia | Bun-first,端對端型別安全 | Bun | TypeScript 原生 |
Express:成熟、生態大、但設計老了
Express(2010)是 Node.js 後端的元老。優點是生態系最完整——幾乎任何功能都能找到 Express middleware;文件和 Stack Overflow 資源最多;幾乎所有 Node.js 後端工程師都看得懂。
// Express:宣告式、沒有魔法
const app = express();
app.use(express.json());
app.use(cors());
app.get('/users/:id', authenticate, async (req, res) => {
const user = await userService.findById(req.params.id);
res.json(user);
});缺點:
req.body永遠是any,TypeScript 只做表面工夫- 路由查找用 layer list,效能比 Fastify / Hono 差(小 API 感受不到,高 QPS 才有差)
- 沒有 schema-based validation,要自己接 express-validator 或 zod
- 沒有 typed route——handler signature 和 URL 沒有型別關聯
適合:熟悉 Express 生態的團隊、需要大量既有 middleware 的場景、prototype 快速開發。
Fastify:Express 的效能升級版
Fastify 解的是 Express 的兩個問題:效能和型別安全。
import Fastify from 'fastify';
const app = Fastify({ logger: true });
// Schema-driven:route schema 讓 body 有型別
app.post('/users', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' },
},
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' },
},
},
},
},
}, async (request, reply) => {
// request.body 在這裡是 typed(透過 JSON Schema 推斷)
const user = await userService.create(request.body);
reply.status(201).send(user);
});Fastify 用 JSON Schema 做 validation 和序列化,比 Express 的 JSON.stringify 快 2-3 倍(用 fast-json-stringify)。
缺點:
- Plugin 系統是 Fastify 的核心設計,不熟悉的人初期會被 encapsulation scope 搞混
- JSON Schema 比 Pydantic / zod 更 verbose,型別推斷不如 zod 自然
- 生態比 Express 小(但在快速追趕中)
適合:需要效能但不想用 NestJS 這麼重的架構;I/O 密集的高 QPS API。
Hono:超輕量、typed route、跨 runtime
Hono(2022)的設計目標是:輕量(比 Express 小 10 倍)、typed、跨 runtime(同一份程式碼可以跑在 Node.js / Bun / Cloudflare Workers / Deno)。
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
const app = new Hono();
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
app.post('/users',
zValidator('json', createUserSchema),
async (c) => {
const data = c.req.valid('json'); // ← 完整型別,不是 any
const user = await userService.create(data);
return c.json(user, 201);
}
);
// RPC style:client 可以用型別安全的方式呼叫
const route = app.get('/users/:id', (c) => {
const id = c.req.param('id'); // ← typed
return c.json({ id });
});
export type AppType = typeof route; // ← client 用這個推斷 API 型別Hono 的 RPC style 讓 client(例如前端)可以用 hono/client import 後端的型別,不需要手寫 API type 或用 OpenAPI codegen。
缺點:
- 生態相對年輕(2022 年,比 Express 晚 12 年)
- Cross-runtime 的能力讓它的 API 設計有些限制(要能在 CF Workers 跑就不能用 Node.js only 的 API)
適合:Edge function / serverless;需要跨 runtime 部署;想要 typed route 但不想用 NestJS。
NestJS:Angular 哲學 in Node.js
NestJS 的設計哲學直接借自 Angular:Module / Injectable / Controller / Guard / Pipe / Interceptor。
// 完整的 DI、lifecycle、module 系統
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UserService],
controllers: [UserController],
})
export class UserModule {}
@Injectable()
export class UserService {
constructor(
@InjectRepository(User) private userRepo: Repository<User>,
) {}
async findById(id: number) {
return this.userRepo.findOneByOrFail({ id });
}
}
@Controller('users')
@UseGuards(AuthGuard)
export class UserController {
constructor(private userService: UserService) {}
@Get(':id')
show(@Param('id', ParseIntPipe) id: number) {
return this.userService.findById(id);
}
}NestJS 的 DI container 讓大型系統的依賴管理不失控;Module 系統讓功能拆分有明確邊界;Guard / Pipe / Interceptor 讓 middleware 邏輯有統一的插入點。
缺點:
- 學習曲線高(要先理解 Module / DI / Decorator 系統)
- 效能比 Fastify / Hono 差(抽象層較多)
- 小型 API 用 NestJS 是殺雞用牛刀
適合:大型企業後端;多人開發需要強制架構 convention;從 Angular 或 Spring 換過來的團隊。
Elysia:Bun-first,端對端型別安全
Elysia 是為 Bun runtime 設計的,充分利用 Bun 的效能優勢:
import { Elysia, t } from 'elysia';
const app = new Elysia()
.post('/users', ({ body }) => {
// body 是完整 typed,從 schema 推斷
return userService.create(body);
}, {
body: t.Object({
name: t.String({ minLength: 1 }),
email: t.String({ format: 'email' }),
}),
response: t.Object({
id: t.Number(),
name: t.String(),
}),
});
// Eden Treaty:client 端型別安全 API 呼叫
const client = treaty<typeof app>('http://localhost:3000');
const { data } = await client.users.post({ name: 'Alice', email: 'alice@example.com' });
// data 是完整 typed,不需要手寫 API typeElysia 的端對端型別安全比 Hono 的 RPC style 更完整。
缺點:
- 需要 Bun runtime(不能跑在 Node.js 上)
- 生態最年輕,很多 middleware 要自己寫
適合:用 Bun 的專案;需要最高效能 + 最強型別安全的場景。
選擇框架的決策路徑
需要跨 runtime(CF Workers / Bun / Node)?
→ 是 → Hono
用 Bun 且在乎最高效能和端對端型別安全?
→ 是 → Elysia
大型系統、多人開發、需要強制架構 convention?
→ 是 → NestJS
需要效能但不要 NestJS 的複雜度?
→ 是 → Fastify
熟悉 Express 生態、不需要高效能、prototype 快速開發?
→ 是 → Express
沒有哪個是「最好的」——Express 在 2026 還在大量使用,不是因為沒有替代方案,而是因為它的 trade-off 在很多場景依然合理。
