Giter Site home page Giter Site logo

fastify-awilix's Introduction

@fastify/awilix

NPM Version Build Status

Dependency injection support for fastify framework, using awilix.

Getting started

First install the package and awilix:

npm i @fastify/awilix awilix

Next, set up the plugin:

const { fastifyAwilixPlugin } = require('@fastify/awilix')
const fastify = require('fastify')

app = fastify({ logger: true })
app.register(fastifyAwilixPlugin, { 
  disposeOnClose: true, 
  disposeOnResponse: true,
  strictBooleanEnforced: true
})

Then, register some modules for injection:

const { 
  diContainer, // this is an alias for diContainerProxy
  diContainerClassic, // this instance will be used for `injectionMode = 'CLASSIC'`
  diContainerProxy // this instance will be used by default
} = require('@fastify/awilix')
const { asClass, asFunction, Lifetime } = require('awilix')

// Code from the previous example goes here

diContainer.register({
  userRepository: asClass(UserRepository, {
    lifetime: Lifetime.SINGLETON,
    dispose: (module) => module.dispose(),
  }),
})

app.addHook('onRequest', (request, reply, done) => {
  request.diScope.register({
    userService: asFunction(
      ({ userRepository }) => {
        return new UserService(userRepository, request.params.countryId)
      },
      {
        lifetime: Lifetime.SCOPED,
        dispose: (module) => module.dispose(),
      }
    ),
  })
  done()
})

Note that there is no strict requirement to use classes, it is also possible to register primitive values, using either asFunction(), or asValue(). Check awilix documentation for more details.

After all the modules are registered, they can be resolved with their dependencies injected from app-scoped diContainer and request-scoped diScope. Note that diScope allows resolving all modules from the parent diContainer scope:

app.post('/', async (req, res) => {
  const userRepositoryForReq = req.diScope.resolve('userRepository')
  const userRepositoryForApp = app.diContainer.resolve('userRepository') // This returns exact same result as the previous line
  const userService = req.diScope.resolve('userService')

  // Logic goes here

  res.send({
    status: 'OK',
  })
})

Plugin options

injectionMode - whether to use PROXY or CLASSIC injection mode. See awilix documentation for details. Default is 'PROXY'.

container - pre-created AwilixContainer instance that should be used by the plugin. By default plugin uses its own instance. Note that you can't specify both injectionMode and container parameters at the same time.

disposeOnClose - automatically invoke configured dispose for app-level diContainer hooks when the fastify instance is closed. Disposal is triggered within onClose fastify hook. Default value is true

disposeOnResponse - automatically invoke configured dispose for request-level diScope hooks after the reply is sent. Disposal is triggered within onResponse fastify hook. Default value is true

asyncInit - whether to process asyncInit fields in DI resolver configuration. Note that all dependencies with asyncInit enabled are instantiated eagerly. Disabling this will make app startup slightly faster. Default value is false

asyncDispose - whether to process asyncDispose fields in DI resolver configuration when closing the fastify app. Disabling this will make app closing slightly faster. Default value is false

eagerInject - whether to process eagerInject fields in DI resolver configuration, which instantiates and caches module immediately. Disabling this will make app startup slightly faster. Default value is false

strictBooleanEnforced - whether to throw an error if enabled field in a resolver configuration is set with unsupported value (anything different from true and false). It is recommended to set this to true, which will be a default value in the next semver major release. Default value is false

Defining classes

All dependency modules are resolved using either the constructor injection (for asClass) or the function argument (for asFunction), by passing the aggregated dependencies object, where keys of the dependencies object match keys used in registering modules:

class UserService {
  constructor({ userRepository }) {
    this.userRepository = userRepository
  }

  dispose() {
    // Disposal logic goes here
  }
}

class UserRepository {
  constructor() {
    // Constructor logic goes here
  }

  dispose() {
    // Disposal logic goes here
  }
}

diContainer.register({
  userService: asClass(UserRepository, {
    lifetime: Lifetime.SINGLETON,
    dispose: (module) => module.dispose(),
  }),
  userRepository: asClass(UserRepository, {
    lifetime: Lifetime.SINGLETON,
    dispose: (module) => module.dispose(),
  }),
})

Typescript usage

By default @fastify/awilix is using generic empty Cradle and RequestCradle interfaces, it is possible extend them with your own types:

awilix defines Cradle as a proxy, and calling getters on it will trigger a container.resolve for an according module. Read more

declare module '@fastify/awilix' {
  interface Cradle {
    userService: UserService
  }
  interface RequestCradle {
    user: User
  }
}
//later, type is inferred correctly
fastify.diContainer.cradle.userService
// or
app.diContainer.resolve('userService')
// request scope
request.diScope.resolve('userService')
request.diScope.resolve('user')

Find more in tests or in example from awilix documentation

Asynchronous init, dispose and eager injection

fastify-awilix supports extended awilix resolver options, provided by awilix-manager:

const { diContainer, fastifyAwilixPlugin } = '@fastify/awilix'
const { asClass } = require('awilix')

diContainer.register(
  'dependency1',
  asClass(AsyncInitSetClass, {
    lifetime: 'SINGLETON',
    asyncInit: 'init',
    asyncDispose: 'dispose',
    eagerInject: true,
  })
)

app = fastify()
await app.register(fastifyAwilixPlugin, { asyncInit: true, asyncDispose: true, eagerInject: true })
await app.ready()

Advanced DI configuration

For more advanced use-cases, check the official awilix documentation

fastify-awilix's People

Contributors

chrismeyers avatar coobaha avatar dancastillo avatar dependabot[bot] avatar fdawgs avatar kibertoad avatar rafaelgss avatar uzlopak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastify-awilix's Issues

Cannot find module '@fastify/awilix/classic'

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.25.2

Plugin version

3.2.0

Node.js version

v20.10.0

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

6.6.6-arch1-1

Description

I getting the following error when a try to use the classic injection mode:

Error: Cannot find module '@fastify/awilix/classic'

Steps to Reproduce

The code in my case is simple:

import {
  fastifyAwilixPlugin,
  FastifyAwilixOptions,
  diContainer,
} from '@fastify/awilix/classic'
import { Lifetime, asClass, asValue } from 'awilix'
import fp from 'fastify-plugin'
import { drizzleClient } from './drizzle'
import openAi from './open-ai'

export interface AwilixPluginOptions extends FastifyAwilixOptions { }

export default fp<AwilixPluginOptions>(
  async (fastify, opts) => {
    await fastify.register(fastifyAwilixPlugin)
  },
  {
    dependencies: ['application-config', 'drizzle-plugin', 'openai-plugin'],
    name: 'awilix-plugin',
  },
)

declare module 'fastify' { }

Expected Behavior

No response

Missing typings for injection mode option

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

Feature was requested and implemented at #29 , but typings was missing

Fixed it in #59

Configure injection mode

๐Ÿš€ Allow change Awilix injection mode

When using Awilix with Typescript, CLASSIC injection mode seems to be more desirable.

Motivation

Awilix creator @jeffijoe comment:

I used to prefer the options object (which was the reason I created Awilix using Proxies to begin with), however the problem with incorrect parameter passing goes away with TypeScript, and so I've started preferring the classic approach personally. Doesn't hurt that it's faster, too, hehe.

Example

fastify.register(fastifyAwilixPlugin, {
  injectionMode: 'CLASSIC' // InjectionMode.CLASSIC
})

Migrate away from `jest`

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

No response

Add an option to simply pass AwilixContainer via the plugin options

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 want to propose an option to pass a custom AwilixContainer to the plugin.

Motivation

Hi, thanks for the great plugin.

Currently, this plugin uses a singleton container declared inside it.
While we can directly access it via import { diContainer } from '@fastify/awilix' and call diContainer.dispose() to reset its contents, I think it's more intuitive if we can just pass a new AwilixContainer when we initialize the plugin.

This proposal is mainly for testing. Sure, we can dispose and re-initialize deps on-the-fly with the method described above, but the option to provide the diContainer makes the test far easier.

Example

export type FastifyAwilixOptions = {
  disposeOnResponse?: boolean
  disposeOnClose?: boolean
  asyncInit?: boolean
  asyncDispose?: boolean
  eagerInject?: boolean
  container?: AwilixContainer<Cradle> // <- default to `diContainer` defined in the plugin
}

// register AwilixContainer with plugin option.
// this way we can easily swap the dependencies in testing environment.
fastify.register(fastifyAwilixPlugin, {
  container: newDIContainer
})

Is it possible to release a new tag?

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

Is it possible to release a new tag?
With the bump of fastify-plugin I think it could be useful to release the new tag with it.

Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.