DEV Community

蕨类植物
蕨类植物

Posted on

A practical guide to implementing a Fastify modular project — Part 2 Initial Fastify Project

Getting started with a Fastify project

Two features of Fastify appeal to me the most: its high performance and the 'everything is a plugin' philosophy. Plugins are designed to be easily added, removed and replaced, and integrate quickly. In this article, I’ll explain how to use these features to quickly launch a modular monolith project.

First, we will outline the project structure in our minds, as shown below:

src/
├────── modules/
│       ├── module-a
│       │   ├──plugins
│       │   ├──routes
│       │   └──index.ts
│       ├── module-b
│       │   ├──plugins
│       │   ├──routes
│       ├── └──index.ts
|       │...other modules
├────── plugins      
├────── routes
└────── app.ts
Enter fullscreen mode Exit fullscreen mode

Specifically:

  • Module A can be a user module, providing capabilities such as /user/api/v1/profile and /user/api/v1/links.
  • Module B can be a payment module, providing capabilities such as /payment/api/v1/wechat and /payment/api/alipay.
  • Plugins are plugins required by the service itself. They can be from the Fastify ecosystem (e.g. cookies, swaggerui, rate-limit) or custom utility and app plugins.
  • 'routes' can be the basic APIs exposed by the service or aggregates integrating internal module APIs to provide functionality.
  • app.ts focuses on starting the service.

The key point is that the module must be encapsulated and made available as a plugin.

Create an app and listen to a port

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}`)
})
Enter fullscreen mode Exit fullscreen mode

Create Module A

Create your first API(api-a.ts):

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

export default routes
Enter fullscreen mode Exit fullscreen mode

Create your second API(api-b.ts):

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

export default routes
Enter fullscreen mode Exit fullscreen mode

Consolidating multiple APIs into index.ts (optional)

If you create multiple single-responsibility APIs and place them in a single folder, you can create an index.ts file to consolidate all the APIs in that module.

import apiA from './api-a'
import apiASecond from './api-b'

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

export default routes
Enter fullscreen mode Exit fullscreen mode

Register to the app

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}`)
})
Enter fullscreen mode Exit fullscreen mode

app.register allows you to register the routes exposed by a module and set a URL prefix. Once this configuration is complete, you can use it:

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

to access the API for Module A, use the local configuration at localhost:3000. You can replace the port number with a different one if you wish.

Use @fastify/autoload with prefix

If you are using @fastify/autoload, the prefix in the above code snippet might not work as expected due to differences in the way certain parameters are used.

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' }
})
Enter fullscreen mode Exit fullscreen mode

The 'routes' folder contains all the API interface files, which are registered in app.ts, as shown below:

import Fastify from 'fastify'
import fp from 'fastify-plugin'

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

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

// ...
Enter fullscreen mode Exit fullscreen mode

As can be seen, once the project has been modularised, it will be highly scalable and exhibit high cohesion and low coupling. It will also allow for more controlled testing of modules with clearly defined boundaries.

Top comments (0)