Skip to content

An official IoC oriented starter pack or plugin for Fastify projects with Awilix #6019

@jean-michelet

Description

@jean-michelet

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 tasksRoutes

5. 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)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions