Fastify 模块化项目实战(二) — 初始化Fastify 项目

开始搭建 Fastify 项目

Fastify最吸引我的特性有两个, 一个是高性能, 另一个就是"一切接插件"的理念, 插件的特点就是可拔插, 可快速替换, 快速集成等. 所以这篇文章会简单介绍如何利用这一特性来快速启动一个modular monolith项目.

首先我们心里大概构思出项目结构, 如下所示:

txt 复制代码
src/
├────── modules/
│       ├── module-a
│       │   ├──plugins
│       │   ├──routes
│       │   └──index.ts
│       ├── module-b
│       │   ├──plugins
│       │   ├──routes
│       ├── └──index.ts
|       │...other modules
├────── plugins      
├────── routes
└────── app.ts

其中:

  • module-a 可以是用户模块, 提供类似于 /user/api/v1/profile, /user/api/v1/links 等能力
  • module-b 可以是支付模块, 提供类似于 /payment/api/v1/wechat, /payment/api/alipay 等能力
  • plugins 是服务本身需要的插件, 可以来自于Fastify生态, 例如cookies, swaggerui, rate-limit 等, 也可以是自定义的utility插件和app插件等
  • routes可以是服务暴露的基础api, 也可以作为aggregate存在来整合模块内部的api来提供能力
  • app.ts专注于启动服务

关键点是模块需要以插件的方式封装并暴露出来.

创建 app 并监听端口

ts 复制代码
import Fastify from 'fastify'

const app = Fastify({
    logger: true
})

app.get('/', async (request, reply) => {
    return { hello: 'world' }
})

app.listen({ port: 3000 }, (err, address) => {
    if (err) {
        app.log.error(err)
        process.exit(1)
    }
    app.log.info(`Server listening at ${address}`)
})

创建模块a

创建第一个api(api-a.ts):

ts 复制代码
async function routes(fastify, options) {
    fastify.get('/hello-first', async (request, reply) => {
        return { hello: 'from a' }
    })
}

export default routes

创建第二个api(api-b.ts):

ts 复制代码
async function routes(fastify, options) {
    fastify.get('/hello-second', async (request, reply) => {
        return { hello: 'from another api in a' }
    })
}

export default routes

整合多个API到index.ts中(可选)

如果创建多个职责单一的api, 并把它们放在一个文件夹下, 那么可以创建一个index.ts文件来整合模块下的所有api:

ts 复制代码
import apiA from './api-a'
import apiASecond from './api-b'

async function routes(fastify, options) {
    fastify.register(apiA)
    fastify.register(apiASecond)
}

export default routes

注册到 app

ts 复制代码
import Fastify from 'fastify'
// import api-a and api-b from index file
import routes from './modules/a/routes/index'

const app = Fastify({
    logger: true
})

app.get('/', async (request, reply) => {
    return { hello: 'world' }
})

// register routes and add prefix with module name
app.register(routes, { prefix: '/a' })

app.listen({ port: 3000 }, (err, address) => {
    if (err) {
        app.log.error(err)
        process.exit(1)
    }
    app.log.info(`Server listening at ${address}`)
})

app.register 可以注册模块暴露的路由, 并可以设置URL前缀, 以上配置完成之后, 你可以通过:

  • GET localhost:3000/a/hello-first
  • GET localhost:3000/a/hello-second

来访问模块a的API, 其中localhost:3000是本地的默认配置, 你可以替换端口号为你自己指定的端口号.

使用 @fastify/autoload 并搭配 prefix

如果你使用了@fastify/autoload, 那么以上示例代码中的prefix恐怕无法如你预期那样生效, 因为一些参数使用方式的不同:

ts 复制代码
import path from 'node:path'
import fastifyAutoload from '@fastify/autoload'
import { FastifyInstance, FastifyPluginOptions } from 'fastify'

fastify.register(fastifyAutoload, {
  dir: path.join(import.meta.dirname, 'routes'),
  options: { ...opts, prefix: '/a' }
})

其中routes文件夹下放着所有的api接口文件, 在 app.ts 中注册模块如下所示:

ts 复制代码
import Fastify from 'fastify'
import fp from 'fastify-plugin'

import moduleARoutes from './modules/a/index.js'

// ...
app.register(fp(moduleARoutes))

// ...

可以看到一旦模块化, 项目将具有很强的可扩展性, 能够展现出高内聚性和低耦合性, 对于模块的测试也更加的可控, 拥有清晰的边界.

原文地址: 原文

相关推荐
程序员buddha2 小时前
TypeScript详细教程
javascript·ubuntu·typescript
We་ct3 小时前
LeetCode 50. Pow(x, n):从暴力法到快速幂的优化之路
开发语言·前端·javascript·算法·leetcode·typescript·
comedate4 小时前
[TypeScript] TypeScript 学习从入门到精通
ubuntu·typescript·前端语言
Wect4 小时前
LeetCode 149. 直线上最多的点数:题解深度剖析
前端·算法·typescript
橘子编程18 小时前
JavaScript与TypeScript终极指南
javascript·ubuntu·typescript
数据知道19 小时前
claw-code 源码分析:从 TypeScript 心智到 Python/Rust——跨栈移植时类型、边界与错误模型怎么对齐?
python·ai·rust·typescript·claude code·claw code
luckyCover1 天前
TypeScript学习系列(二):高级类型篇
前端·typescript
qq_381338501 天前
TypeScript 类型安全与类型体操实战:从入门到精通
javascript·安全·typescript
We་ct1 天前
LeetCode 69. x 的平方根:两种解法详解
前端·javascript·算法·leetcode·typescript·平方
zhensherlock1 天前
Protocol Launcher 系列:Mail Assistant 轻松发送 HTML 邮件
前端·javascript·typescript·node.js·html·github·js