log
码中赤兔

Fastify 最佳实践 #2: autoLoad

发布于 2025年6月21日
更新于 2025年6月26日
15 分钟阅读
TypeScript

在任何 Web 框架的开发中,随着项目规模的增长,如何高效、清晰地组织代码,尤其是路由,都成为一个核心问题。一个良好的自动化配置流程,能极大地提升我们的开发体验和项目的可维护性。

痛点:手动注册的烦恼

在 Fastify 中,添加一个 API 路由非常直接:

// index.ts
import fastify from 'fastify';

const server = fastify();

// 新增一个 API
server.get('/ping', async (request, reply) => {
  return 'pong\n';
});

// 启动服务器...

当项目只有一个文件时,这看起来很简单。但随着 API 逐渐增多,我们自然会希望将每个路由的逻辑拆分到独立的文件中,以保持代码的整洁。于是,代码结构演变成了这样:

// routes/ping.ts
import { FastifyInstance, FastifyPluginOptions } from 'fastify';

// 好的实践是把路由封装在 Fastify 插件中
export default async (fastify: FastifyInstance, opts: FastifyPluginOptions) => {
  fastify.get('/ping', async (request, reply) => {
    return 'pong\n';
  });
};
// index.ts
import pingRoute from './routes/ping';
import fastify from 'fastify';

const server = fastify();

// 每增加一个路由文件,就需要在这里手动注册一次
server.register(pingRoute);

// server.register(userRoute);
// server.register(productRoute);
// ... 更多路由

此时,你会发现两个显而易见的问题:

  1. 手动维护的负担:每创建一个新的路由文件,都必须记得到主入口文件(index.ts)中手动 importregister。这不仅繁琐,还容易遗漏。
  2. 隐性的重复:路由文件的路径(例如 routes/ping.ts)和它所定义的 API 端点(/ping)在语义上是重复的。

我们能否让文件结构直接映射为 API 路径?为了解决这些问题,Fastify 官方生态提供了一个强大的利器:@fastify/autoload

解法:@fastify/autoload 实现自动化

@fastify/autoload 允许我们指定一个目录,它会自动扫描该目录下的所有文件,并将它们作为插件(包括路由)注册到 Fastify 实例中。

1. 自动化加载路由

假设我们需要定义以下几个 API:

POST /management/user
DELETE /management/user/{id}
GET /user
GET /user/{id}

我们可以通过创建对应的文件和目录结构,让 autoload 自动生成这些路由。按照约定,像 {id} 这样的路径参数需要用下划线前缀的目录或文件名来表示,例如 _id

目录结构可以设计成这样:

src
 ┣ routes
 ┃ ┣ management
 ┃ ┃ ┗ user
 ┃ ┃ ┃ ┣ _id
 ┃ ┃ ┃ ┃ ┗ delete.ts  // -> DELETE /management/user/:id
 ┃ ┃ ┃ ┗ post.ts      // -> POST /management/user
 ┃ ┗ user
 ┃ ┃ ┣ _id
 ┃ ┃ ┃ ┗ get.ts       // -> GET /user/:id
 ┃ ┃ ┗ get.ts         // -> GET /user
 ┣ plugins
 ┃ ┗ ...
 ┗ index.ts

接下来,我们编写路由文件。由于 autoload 会根据文件路径生成路由前缀,所以在路由文件中,我们只需定义相对于该前缀的路径。通常,对于 index.ts 或与 HTTP 方法同名的文件(如 get.ts, post.ts),路径可以留空 ”。

// src/routes/management/user/post.ts
import { FastifyInstance } from 'fastify';

// 使用默认导出,函数必须是 async 的
export default async function (fastify: FastifyInstance) {
  // 此处的路径为空字符串,autoload 会自动加上 /management/user 前缀
  fastify.post('', async (_request, reply) => {
    // 实际的业务逻辑
    return reply.status(201).send({ message: 'User created' });
  });
}

最后,在主入口文件 index.ts 中配置 autoload

// src/index.ts
import autoLoad from '@fastify/autoload';
import fastify from 'fastify';
import { join } from 'path';

const server = fastify({ logger: true });

// 注册路由自动加载
server.register(autoLoad, {
  dir: join(__dirname, 'routes'), // 指定路由所在的目录
  routeParams: true,
  dirNameRoutePrefix: true,
});

server.listen({ port: 3000 });

autoLoad 核心选项解析

  • dir: 必须指定的选项,指向你的路由或插件所在的根目录。
  • routeParams: true: 这是实现动态路由的关键。启用后,autoload 会将下划线 _ 前缀的文件或目录名(如 _id)转换为 Fastify 的路径参数(如 /:id)。
  • dirNameRoutePrefix: true: 告诉 autoload 使用目录结构来生成路由前缀。关闭此项,routes/management/user/post.ts 将只会注册为 /post 而不是 /management/user/post

2. 自动化加载插件

除了路由,autoload 同样可以加载自定义插件。插件是封装可复用逻辑(如数据库连接、身份验证、装饰器等)的绝佳方式。为了更好地管理插件之间的依赖和封装,我们通常会使用 fastify-plugin

首先,安装依赖:

npm i fastify-plugin

现在,我们来创建一个插件,它向 Fastify 实例上“装饰”一个配置对象。

// src/plugins/config.plugin.ts
import { FastifyPluginAsync } from 'fastify';
import fp from 'fastify-plugin';

// 使用 fp() 包裹插件,可以防止 Fastify 的封装机制隔离插件
// 这意味着这个插件中装饰的内容(decorate)将对外部所有子上下文可见
const configPlugin: FastifyPluginAsync = async (fastify, opts) => {
  const config = {
    dbUrl: process.env.DB_URL || 'default-db-url',
    apiKey: process.env.API_KEY,
  };

  // 使用 decorate 将配置对象附加到 Fastify 实例
  fastify.decorate('config', config);
};

export default fp(configPlugin);

我们可以把所有插件都放在一个专用的 plugins 目录中。然后,再次使用 autoload 来加载它们。

// 在 index.ts 中继续添加
// ...
// 注册插件自动加载
server.register(autoLoad, {
  dir: join(__dirname, 'plugins'),
  // 使用 matchFilter 可以更精细地控制加载哪些文件
  matchFilter: (path) => path.includes('plugin'),
});

// 注册路由 (注意,插件应该在路由之前注册)
server.register(autoLoad, {
  dir: join(__dirname, 'routes'),
  // ...
});

matchFilter 选项非常有用,它接受一个文件路径并返回一个布尔值,允许你根据文件名、扩展名或其他规则来决定是否加载该文件。

总结

@fastify/autoload 是一个看似简单却极其强大的工具。通过拥抱“约定优于配置”的理念,它能帮助我们:

  • 消除样板代码:不再需要手动 importregister 每个路由和插件。
  • 优化项目结构:通过文件系统的目录结构来直观地反映 API 结构。
  • 提升开发效率:开发者只需关注业务逻辑的实现,其余的交给自动化工具。

在一个不断成长的项目中,尽早引入 autoLoad 是一种明智的投资。它能让你的 Fastify 应用从一开始就保持高度的组织性和可维护性。

关于

分享技术见解、经验和思考的个人博客

联系方式

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 码中赤兔. 版权所有