-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
An official IoC oriented starter pack or plugin for Fastify projects with Awilix #6019
Description
Prerequisites
- I have written a descriptive issue title
- I have searched existing issues to ensure the feature has not already been requested
🚀 Feature Proposal
I would like to propose a new official starter pack (or plugin) designed to simplify the creation of Dependency Injection (DI)-driven applications with Inversion of Control (IoC) container. This would provide a solid foundation for structuring applications similar to DI-centric frameworks like Nest.js, Symfony, Spring, or ASP.NET Core, leveraging fastify-awilix.
Motivation
Fastify ecosystem currently lack a standardized way to adopt IoC container based development while maintaining Fastify’s lightweight and flexible nature. I would like to provide a ready-to-use structure that helps teams onboard quickly, removing the cognitive overhead of defining conventions, and project organization from scratch.
If we succeed making it easier to configure the IoC container, it would provide a credible lightweight alternative to existing Node.js framework like Nest.js or Adonis.
Additionally, it addresses a common concerns with Fastify decorators and TypeScript module augmentation for custom logic. A kind of encapsulation can probably be implemented with scoped container feature?
Example
Here’s an example of a simple “Tasks” domain.
1. Define the Domain Interfaces
// Service name in container
export const TASKS_REPOSITORY = Symbol('TASKS_REPOSITORY')
export interface ITasksRepository {
findAll: () => Promise<Task[]>
}2. Create the Domain Service for business logic
import { asClass, Lifetime } from 'awilix'
interface Dependencies {
[TASKS_REPOSITORY]: ITasksRepository
}
export class TasksService {
private readonly tasksRepository: ITasksRepository
constructor (deps: Dependencies) {
this.tasksRepository = deps[TASKS_REPOSITORY]
}
public getAll () {
return this.tasksRepository.findAll()
}
}
// IoC provider
export const TASKS_SERVICE = Symbol('TASKS_SERVICE')
export const TasksServiceProvider = {
[TASKS_SERVICE]: asClass(TasksService, {
lifetime: Lifetime.SINGLETON
})
}3. Provide a concrete repository for interface ITasksRepository
import { asClass, Lifetime } from 'awilix'
export class InMemoryTasksRepository implements ITasksRepository {
async findAll () {
return [
{
id: 1,
author_id: 1,
name: 'task 1',
status: 'NEW',
}
]
}
}
// IoC provider
export const InMemoryTasksRepositoryProvider = {
[TASKS_REPOSITORY]: asClass(InMemoryTasksRepository, {
lifetime: Lifetime.SINGLETON
})
}4. HTTP Plugin
const tasksRoutes: FastifyPluginAsyncTypebox = async (fastify) => {
const tasksService = fastify.diContainer.resolve<TasksService>(TASKS_SERVICE)
fastify.get(
'/api/tasks',
{
schema: {
response: {
200: TaskSchema[]
},
tags: ['Tasks']
}
},
async () => {
return tasksService.getAll()
}
)
}
export default tasksRoutes5. Domain Plugin
export const tasksPlugin = fp(
async function (fastify) {
fastify.diContainer.register(InMemoryTasksRepositoryProvider)
fastify.diContainer.register(TasksServiceProvider)
fastify.register(tasksRoutes)
},
{ name: 'tasks' }
)6. Main App
export default async function serviceApp (
fastify: FastifyInstance,
opts: FastifyPluginOptions
) {
// Load external plugins, mainly fastify core plugins, env config, etc.
await fastify.register(fastifyAutoload, {
dir: path.join(import.meta.dirname, 'plugins/external'),
options: { ...opts }
})
// Register Awilix IoC container
fastify.register(fastifyAwilixPlugin, {
disposeOnClose: true,
disposeOnResponse: true,
strictBooleanEnforced: true
})
// Register domain plugin
fastify.register(tasksPlugin)
}