现代后端框架

选讲

Fastify / Nest.js / Hono——找到最适合你的那把刀

🎯 学习目标

学完本章,你将能够:

  • 理解 Fastify 的插件系统和 Schema 验证机制
  • 掌握 Nest.js 的依赖注入、控制器和服务架构
  • 了解 Hono 的轻量级路由和多平台部署能力
  • 根据项目需求选择合适的后端框架

为什么还要学别的

Express 够用了,为什么还要看其他框架?

Express 是"够用"的,但它有几个短板:没有内置验证、性能一般、TypeScript 支持靠社区补丁。

现代框架在这些方面做了显著改进。了解它们的思路,能帮你做出更好的技术选型。即使继续用 Express,也能从中学到更好的架构模式。

框架全景

一张表看清四个主流框架的特点

框架性能TS 支持适用场景
Express中需 @types快速原型、学习
Fastify高原生支持高性能 API
Nest.js中一流大型企业应用
Hono极高原生支持Edge / Serverless

选择建议:学习用 Express,新项目用 Fastify 或 Hono,大型企业应用用 Nest.js。

Part 1: Fastify

高性能 Node.js 框架,内置验证与插件生态

Fastify 的核心卖点是比 Express 快 2-3 倍的吞吐量,同时内置了 JSON Schema 验证和原生的 TypeScript 支持。它的插件系统有明确的作用域和生命周期,适合构建结构清晰的中大型 API 服务。

Fastify 路由

类型安全的路由,参数自动推断

$ npm install fastify
import Fastify from "fastify";

const app = Fastify({ logger: true });

// 类型安全路由——泛型定义参数类型
app.get<{ Params: { id: string } }>("/users/:id", async (req, reply) => {
    const { id } = req.params; // 自动推断为 string
    return { id, name: "张三" };
});

// 带验证的 POST
app.post<{ Body: { name: string; email: string } }>("/users", async (req, reply) => {
    const { name, email } = req.body;
    return { id: "1", name, email };
});

app.listen({ port: 3000 });

插件系统

用插件扩展 Fastify 的能力,类似 Express 的中间件

import fp from "fastify-plugin";

// 定义插件
export default fp(async (app) => {
    app.decorate("db", new Database());
}, { name: "database" });

// 声明插件类型
declare module "fastify" {
    interface FastifyInstance {
        db: Database;
    }
}

// 使用:在任意路由中访问 app.db
app.get("/users", async (req, reply) => {
    const users = await app.db.findAll(); // 有类型提示
    return users;
});

Fastify 的插件系统比 Express 中间件更强大:插件有明确的作用域(可以限制到某个路由前缀),而且支持异步初始化。

Schema 验证

Fastify 的杀手级特性:用 JSON Schema 同时验证请求和响应

app.post("/users", {
    schema: {
        body: {
            type: "object",
            required: ["name", "email"],
            properties: {
                name: { type: "string", minLength: 2 },
                email: { type: "string", format: "email" },
                age: { type: "integer", minimum: 0 }
            }
        },
        response: {
            201: {
                type: "object",
                properties: {
                    id: { type: "string" },
                    name: { type: "string" }
                }
            }
        }
    }
}, async (req, reply) => {
    reply.code(201);
    return { id: "1", name: req.body.name };
});

JSON Schema 验证在 Fastify 中是零成本的——它直接编译成高性能的验证函数,不仅不会拖慢请求,还能让 Fastify 更快地序列化响应。如果你更喜欢 Zod,可以使用 fastify-type-provider-zod 插件来替换默认的 JSON Schema。

请求生命周期 Hooks

在请求的各个阶段插入自定义逻辑:认证、日志、响应改写

// 认证 Hook —— 在所有 /api 路由前执行
app.addHook("onRequest", async (req, reply) => {
    const token = req.headers.authorization;
    if (!token) {
        reply.code(401).send({ error: "未认证" });
        return; // 阻止请求继续
    }
    req.user = verifyToken(token);
});

// 日志 Hook —— 请求完成后记录耗时
app.addHook("onResponse", (req, reply, done) => {
    req.log.info({
        url: req.url,
        method: req.method,
        statusCode: reply.statusCode,
        responseTime: reply.elapsedTime
    }, "请求完成");
    done();
});

// 响应改写 Hook —— 统一包装返回格式
app.addHook("preSerialization", async (req, reply, payload) => {
    return { success: true, data: payload };
});

Fastify 的 Hook 按生命周期执行:onRequest → preParsing → preValidation → preHandler → preSerialization → onSend → onResponse。你可以精确控制逻辑插入的时机。

Part 2: Nest.js

Angular 风格的企业级框架,装饰器 + 依赖注入 + 模块化

Nest.js 采用装饰器和依赖注入来组织代码,天生适合大型团队协作。它强制模块化架构,内置了守卫(Guard)、拦截器(Interceptor)、管道(Pipe)等概念,让权限控制、日志、数据转换等横切关注点有了标准化的写法。

Nest 架构

三大核心概念:模块、控制器、服务

图:Nest.js 模块化架构层次关系

控制器与服务

装饰器定义路由,服务处理逻辑

$ npm install @nestjs/core @nestjs/common @nestjs/platform-express
// user.controller.ts
import { Controller, Get, Post, Body, Param } from "@nestjs/common";

@Controller("users")
export class UserController {
    constructor(private readonly userService: UserService) {}

    @Get()
    findAll(): Promise<User[]> {
        return this.userService.findAll();
    }

    @Get(":id")
    findOne(@Param("id") id: string): Promise<User> {
        return this.userService.findOne(id);
    }

    @Post()
    create(@Body() dto: CreateUserDto): Promise<User> {
        return this.userService.create(dto);
    }
}

DTO 验证

用装饰器定义验证规则,请求进来时自动校验

import { IsString, IsEmail, IsOptional, MinLength } from "class-validator";

export class CreateUserDto {
    @IsString()
    @MinLength(2)
    name!: string;

    @IsEmail()
    email!: string;

    @IsOptional()
    @MinLength(6)
    password?: string;
}

// user.module.ts —— 组装模块
import { Module } from "@nestjs/common";

@Module({
    controllers: [UserController],
    providers: [UserService],
})
export class UserModule {}

项目结构与 CLI

Nest CLI 一键生成模块骨架,团队协作更高效

# 安装 CLI 并创建项目
$ npm install -g @nestjs/cli
$ nest new my-project
$ cd my-project
src/
├── app.module.ts          # 根模块
├── main.ts                # 入口文件
├── common/                # 共享模块
│   ├── guards/            # 守卫
│   ├── interceptors/      # 拦截器
│   └── pipes/             # 管道
└── modules/
    ├── user/
    │   ├── user.module.ts
    │   ├── user.controller.ts
    │   ├── user.service.ts
    │   └── dto/
    │       └── create-user.dto.ts
    └── post/
        ├── post.module.ts
        ├── post.controller.ts
        └── post.service.ts

统一的目录结构让团队成员可以快速定位代码。每个模块自包含(controller + service + dto),模块之间通过 exports 显式共享服务,避免隐式依赖。

Guards 守卫

用守卫控制访问权限,比中间件更优雅的权限方案

// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { Reflector } from "@nestjs/core";

@Injectable()
export class RolesGuard implements CanActivate {
    constructor(private reflector: Reflector) {}

    canActivate(context: ExecutionContext): boolean {
        // 从装饰器读取所需角色
        const requiredRoles = this.reflector.get(
            "roles", context.getHandler()
        );
        if (!requiredRoles) return true; // 没有标记则放行

        const request = context.switchToHttp().getRequest();
        const user = request.user;
        return requiredRoles.some(role => user.roles?.includes(role));
    }
}

// 自定义装饰器
import { SetMetadata } from "@nestjs/common";
export const Roles = (...roles: string[]) => SetMetadata("roles", roles);

// 使用:只有 admin 角色可以访问
@Post("admin/users")
@Roles("admin")
@UseGuards(RolesGuard)
createAdmin(@Body() dto: CreateUserDto) { ... }

Interceptors 拦截器

统一处理响应格式和日志,AOP 编程思想的实践

// 响应格式化拦截器
import { Injectable, NestInterceptor, CallHandler } from "@nestjs/common";
import { map } from "rxjs/operators";

@Injectable()
export class TransformInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler) {
        return next.handle().pipe(
            map(data => ({
                success: true,
                data,
                timestamp: new Date().toISOString()
            }))
        );
    }
}

// 日志拦截器 —— 记录每个请求的耗时
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler) {
        const request = context.switchToHttp().getRequest();
        const now = Date.now();
        console.log(`[→] ${request.method} ${request.url}`);
        return next.handle().pipe(
            tap(() => console.log(`[←] ${Date.now() - now}ms`))
        );
    }
}

// 全局注册
app.useGlobalInterceptors(
    new TransformInterceptor(),
    new LoggingInterceptor()
);

依赖注入与模块化

Nest 的 DI 容器自动管理实例的创建和注入

// 1. 定义可注入的服务
import { Injectable } from "@nestjs/common";

@Injectable()
export class UserService {
    private users: User[] = [];

    async findAll(): Promise<User[]> { return this.users; }
    async create(dto: CreateUserDto): Promise<User> { ... }
}

// 2. 组装模块 —— 声明依赖关系
import { Module } from "@nestjs/common";

@Module({
    controllers: [UserController],   // 处理路由
    providers: [UserService],         // 提供服务
    exports: [UserService],           // 允许其他模块使用
})
export class UserModule {}

// 3. 根模块引入子模块
@Module({ imports: [UserModule, PostModule] })
export class AppModule {}
# CLI 快速生成模块、控制器、服务
$ nest g module user
$ nest g controller user
$ nest g service user

Nest 的依赖注入让你不需要手动 new 对象——容器会根据构造函数参数自动注入。模块(Module)则定义了哪些服务对外可见,避免全局状态污染。

Part 3: Hono

极轻量的跨运行时框架,Edge 优先的 Web 开发

Hono 意为"火焰",是一个极轻量的跨运行时框架。同一份代码可以在 Node.js、Bun、Deno、Cloudflare Workers 上运行,包体积仅约 14KB。它内置 RPC 类型推断,让你在前端调用后端 API 时获得完整的类型安全,无需手写类型定义。

Hono 路由

极简 API,上手只需 5 分钟

$ npm install hono
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";

const app = new Hono();

// 中间件
app.use("*", cors());
app.use("*", logger());

// 类型安全路由
app.get("/users/:id", (c) => {
    const id = c.req.param("id");     // 自动推断 string
    return c.json({ id, name: "张三" });
});

export default app;

Hono 类型安全

通过泛型定义上下文变量,中间件和路由共享类型

interface Env {
    Variables: {
        userId: string;
    };
}

const app = new Hono<Env>();

app.use("/api/*", async (c, next) => {
    const token = c.req.header("Authorization");
    if (!token) return c.json({ error: "未认证" }, 401);
    c.set("userId", verifyToken(token));
    await next();
});

app.get("/api/me", (c) => {
    const userId = c.get("userId"); // string —— 有类型提示
    return c.json({ userId });
});

Hono 最大的优势是跨运行时。如果你的项目可能部署在 Edge(Cloudflare Workers、Vercel Edge Functions),Hono 是最佳选择。

Hono RPC 类型安全

前后端共享路由类型,调用 API 时自动补全参数和返回值

// server.ts —— 定义路由并导出类型
import { Hono } from "hono";

const app = new Hono()
    .get("/users", (c) => c.json([{ id: "1", name: "张三" }]))
    .post("/users", async (c) => {
        const body = await c.req.json();
        return c.json({ id: "2", ...body }, 201);
    });

export type AppType = typeof app;  // 导出路由类型

// client.ts —— 前端调用,自动补全
import { hc } from "hono/client";
import type { AppType } from "./server";

const client = hc<AppType>("/");

// 完整的类型推断!
const res = await client.users.$get();
const users = await res.json();    // { id: string; name: string }[]

const createRes = await client.users.$post({
    json: { name: "李四", email: "li@test.com" }
});

Hono RPC 的核心是 hc<> 客户端——它从服务端路由定义自动推断每个 API 的请求参数和响应类型,实现真正的端到端类型安全,类似于 tRPC 的体验但不需要额外协议。

Hono 数据验证

用 Zod 定义 Schema,在中间件层就拦截无效请求

import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";

const schema = z.object({
    name: z.string().min(2, "姓名至少2个字符"),
    email: z.string().email("邮箱格式不正确"),
    age: z.number().int().min(0).optional()
});

const app = new Hono()
    .post("/users", zValidator("json", schema, (result, c) => {
        // 验证失败的回调
        if (!result.success) {
            return c.json({ error: result.error.flatten() }, 400);
        }
    }), (c) => {
        // 验证通过,数据有完整类型
        const { name, email, age } = c.req.valid("json");
        return c.json({ id: "1", name, email }, 201);
    });

@hono/zod-validator 支持 json、form、query、param 等多种验证目标。验证通过后,c.req.valid("json") 返回的是已经过 Zod 推断的类型安全数据。

中间件链与多平台部署

内置丰富中间件,一行命令部署到 Edge 运行时

import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { prettyJSON } from "hono/pretty-json";
import { bearerAuth } from "hono/bearer-auth";
import { cache } from "hono/cache";

const app = new Hono();

// 类型安全的中间件链
app.use("*", logger());
app.use("/api/*", cors());
app.use("/api/*", prettyJSON());
app.use("/api/admin/*", bearerAuth({ token: "secret" }));

// 缓存策略
app.get("/api/posts", cache({ cacheName: "posts", cacheControl: "max-age=60" }),
    (c) => c.json(posts)
);

export default app;
# 部署到不同平台
$ npx wrangler deploy     # Cloudflare Workers
$ npx vercel deploy       # Vercel Edge Functions
$ npx deploy-to-deno      # Deno Deploy

Hono 的中间件用 app.use() 按顺序注册,支持路径匹配和通配符。得益于统一的 Web Standard API,同一份代码可以直接部署到 Cloudflare Workers、Vercel Edge、Deno Deploy 等多种平台。

错误处理与超时

Fastify 的错误处理比 Express 更规范:自定义错误类 + 全局处理器

// 自定义业务错误
import { FastifyError } from "fastify";

class NotFoundError extends Error {
    statusCode = 404;
    constructor(message: string) {
        super(message);
        this.name = "NotFoundError";
    }
}

// 全局错误处理器
app.setErrorHandler((error, req, reply) => {
    if (error.validation) {
        // Schema 验证失败
        reply.code(400).send({
            success: false,
            error: "请求参数验证失败",
            details: error.validation
        });
    } else if (error instanceof NotFoundError) {
        reply.code(404).send({ success: false, error: error.message });
    } else {
        req.log.error(error);
        reply.code(500).send({ success: false, error: "服务器内部错误" });
    }
});

// 请求超时保护
app.get("/slow-route", {
    config: { timeout: 5000 }  // 5秒超时
}, async (req, reply) => { ... });

Fastify 自动区分验证错误(error.validation)和其他错误。结合 FST_ERR_CTP_EMPTY_JSON_BODY 等内置错误码,你可以针对每种情况返回精确的响应。

框架选型指南

不同场景该选哪个框架?一张流程图帮你做决定

图:Node.js 后端框架选型决策流程图
场景推荐框架理由
学习 / 快速原型Express文档最多,社区最大,踩坑成本最低
高性能 REST APIFastify吞吐量最高,内置验证,插件生态成熟
大型企业应用Nest.js强约束架构,DI + 模块化,团队协作规范
Edge / ServerlessHono体积最小,跨运行时,RPC 类型安全

实用建议:如果你不确定选哪个,从 Fastify 开始。它性能好、学习曲线平缓,绝大多数项目都够用。等团队规模变大、需要更强的架构约束时,再考虑迁移到 Nest.js。

常见问题

不要在没有明确需求时频繁切换框架。 框架是工具,不是信仰。选一个用熟比什么都好。

问题一:Fastify 插件的加载顺序不对

原因:Fastify 插件是异步加载的,依赖关系没有正确声明。

修复:在 fp() 中通过 dependencies 参数声明依赖关系。

问题二:Nest.js 的 DTO 验证不生效

原因:忘记在 main.ts 中启用 ValidationPipe。

修复:app.useGlobalPipes(new ValidationPipe({ whitelist: true }));

问题三:Hono 在 Node.js 中报错

原因:Hono 默认为 Edge 环境,需要额外安装 @hono/node-server。

修复:npm install @hono/node-server,然后用 serve(app) 启动。

试一试:Fastify

练习一

任务:用 Fastify 创建一个 GET /health 接口,返回 { status: "ok" }。

// 提示:Fastify 路由直接 return 对象即可
// 你的代码:

参考答案

import Fastify from "fastify";
const app = Fastify();

app.get("/health", async (req, reply) => {
    return { status: "ok" };
});

app.listen({ port: 3000 });

试一试:Hono + Nest

练习二

任务:用 Hono 写一个带认证中间件的路由,只有带 Authorization 头的请求才能访问 /api/me。

// 提示:检查 c.req.header("Authorization")
// 你的代码:

参考答案

const app = new Hono();

app.use("/api/*", async (c, next) => {
    const token = c.req.header("Authorization");
    if (!token) return c.json({ error: "未认证" }, 401);
    await next();
});

app.get("/api/me", (c) => {
    return c.json({ user: "Alice" });
});

📝 要点总结

本章核心知识点

Fastify

高性能 + Schema 验证 + 插件系统,适合追求极致性能的 API 服务

Nest.js

依赖注入 + 模块化 + 装饰器,适合大型企业级应用

Hono

轻量 + 类型安全 + 多平台,适合边缘计算和 Serverless 场景

选型原则

没有最好的框架,只有最适合项目规模和团队经验的选择

下一章

框架只是入口,数据才是核心。下一章学习数据持久化——用 Prisma 连接数据库,让数据真正"存下来"。

1 / X