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
Specifically:
- Module A can be a user module, providing capabilities such as
/user/api/v1/profileand/user/api/v1/links. - Module B can be a payment module, providing capabilities such as
/payment/api/v1/wechatand/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.tsfocuses 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}`)
})
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
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
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
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}`)
})
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' }
})
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))
// ...
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)