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))

// ...

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

原文地址: 原文

相关推荐
蕨类植物3 小时前
Fastify 模块化项目实战 - 你真的懂 tsconfig.json 吗?
typescript
We་ct3 小时前
LeetCode 74. 搜索二维矩阵:两种高效解题思路
前端·算法·leetcode·矩阵·typescript·二分查找
回到原点的码农4 小时前
TypeScript 与后端开发Node.js
javascript·typescript·node.js
Irene19914 小时前
TypeScript 中,Pick 和 Omit 是两个非常常用且强大的内置工具类型(Utility Types)
typescript·pick·omit
江澎涌4 小时前
鸿蒙动态导入实战
android·typescript·harmonyos
SuperEugene20 小时前
TypeScript+Vue 实战:告别 any 滥用,统一接口 / Props / 表单类型,实现类型安全|编码语法规范篇
开发语言·前端·javascript·vue.js·安全·typescript
We་ct21 小时前
LeetCode 35. 搜索插入位置:二分查找的经典应用
前端·算法·leetcode·typescript·个人开发
zhensherlock1 天前
Protocol Launcher 系列:App Store 精准引流与应用推广
javascript·macos·ios·typescript·iphone·mac·ipad
zhensherlock1 天前
Protocol Launcher 系列:Trae AI 编辑器的深度集成
javascript·人工智能·vscode·ai·typescript·编辑器·ai编程