awilix

repository·master·Indexed 26 days ago

https://github.com/jeffijoe/awilix

A powerful and performant Dependency Injection (DI) container for JavaScript and Node.js, written in TypeScript. Awilix allows developers to write composable, testable software without special annotations, decoupling application logic from the DI mechanism. It supports multiple injection modes (PROXY and CLASSIC), lifetime management (TRANSIENT, SCOPED, SINGLETON), and automated module registration via loadModules. Version 13.0.5.

Tokens
8.3K
Snippets
19
Records
54
Agent score
87%

What's inside awilix

  1. Explore the Awilix ecosystem

    master

    There are several community-maintained packages and integrations for Awilix:

    • awilix-manager: Wrapper for eager injection, asynchronous init methods, and dependency lookup by tags.
    • awilix-express: Bindings for the Express HTTP library.
    • awilix-koa: Bindings for the Koa HTTP library.
    • awilix-router-core: Library for building HTTP bindings for Awilix with routing.
    • fastify-awilix: Bindings for the Fastify framework.
    • awilix-vite: Use Awilix in Vite projects.
    • awilix-modular: Modular DI library bringing NestJS-like module architecture to Node.js applications.
  2. Basic Usage of Awilix

    master

    To use Awilix, you need to perform three main steps: create a container, register modules, and resolve dependencies. You can use PROXY injection mode (default) or CLASSIC mode. Enabling strict: true is highly recommended for correctness checks.

    const awilix = require('awilix')
    
    // Create the container
    const container = awilix.createContainer({
      injectionMode: awilix.InjectionMode.PROXY,
      strict: true,
    })
    
    class UserController {
      constructor(opts) {
        this.userService = opts.userService
      }
    
      getUser(ctx) {
        return this.userService.getUser(ctx.params.id)
      }
    }
    
    // Registering a class
    container.register({
      userController: awilix.asClass(UserController),
    })
    
    // Registering a function (factory)
    const makeUserService = ({ db }) => ({
      getUser: (id) => db.query(`select * from users where id=${id}`),
    })
    
    container.register({
      userService: awilix.asFunction(makeUserService),
    })
    
    // Registering a value
    container.register({
      connectionString: awilix.asValue('some-connection-string'),
    })
    
    // Resolving dependencies
    const userController = container.resolve('userController')
    // OR using the cradle proxy
    const userControllerProxy = container.cradle.userController
  3. Run the Awilix Koa example

    master

    This example demonstrates how to use Awilix scopes to pass request-specific state (like currentUser) to framework-independent services. It showcases a pattern where repositories are constructed once (singleton/singleton-like) while services are resolved per request with request-specific dependencies.

    npm install
    npm start
  4. Use Scoped Lifetimes for Web Applications

    master

    In web frameworks, you can use container.createScope() to create a child container for every incoming request. This allows you to register request-specific data (like the current user) that is only available within that specific request's scope.

    const { createContainer, asClass, asValue } = awilix
    const container = createContainer()
    
    class MessageService {
      constructor({ currentUser }) {
        this.user = currentUser
      }
      getMessages() {
        return this.user.id
      }
    }
    
    container.register({
      messageService: asClass(MessageService).scoped(),
    })
    
    // Middleware pattern
    app.use((req, res, next) => {
      // Create a scoped container for this request
      req.scope = container.createScope()
    
      // Register request-specific data
      req.scope.register({
        currentUser: asValue(req.user),
      })
    
      next()
    })
    
    app.get('/messages', (req, res) => {
      // Resolve the service from the request-specific scope
      const messageService = req.scope.resolve('messageService')
      const id = messageService.getMessages()
      res.send(200, id)
    })
  5. Enable Strict Mode for correctness checks

    master

    Strict mode (introduced in Awilix 10) enables additional correctness checks to catch bugs early. It performs the following checks:

    • Lifetime Leak Prevention: Throws an error if a singleton or scoped registration depends on a transient non-value registration. This prevents shorter-lifetime dependencies from leaking outside their intended lifetime.
    • Singleton Scope Enforcement: Disables singleton registrations on any scopes other than the root container to prevent unpredictable behavior.
    • Singleton Resolution Safety: Ensures singleton resolution uses registrations from the root container only, preventing scoped registrations from being preserved in singletons.
  6. Configure Injection Modes

    master

    Awilix supports two injection modes via awilix.InjectionMode. The mode determines how dependencies are passed to functions or constructors.

    InjectionMode.PROXY (Default)

    Injects a proxy object that looks like a regular object. This is recommended for environments where code is minified (e.g., browsers) and allows for more readable tests because parameter order does not matter.

    InjectionMode.CLASSIC

    Parses function/constructor parameters and matches them with container registrations. This mode is much faster during resolution but has a higher initialization cost.

    WARNING: Do not use CLASSIC if you minify your code, as minifiers mangle parameter names which CLASSIC relies on to identify dependencies.

    Injection modes can be set container-wide, per resolver, or for auto-loaded modules. The most specific setting wins.

  7. Install Awilix via UMD (Browser Support)

    master

    For browser-based environments, you can use the UMD build from unpkg.

    <script src="https://unpkg.com/awilix/lib/awilix.umd.js" />
    <script>
      const container = Awilix.createContainer()
    </script>
  8. Automate registration with `loadModules`

    master

    Use loadModules to automatically register classes and functions from files using glob patterns.

    Important Rules:

    • Default Exports: Auto-loading looks at the file's default export. This can be module.exports, module.exports.default, or export default.
    • Non-Default Exports: To load named exports, you must attach the [RESOLVER] property to the export.
    • Compatibility: Auto-loading relies on glob and does not work with bundlers like Webpack, Rollup, or Browserify.

    Configuration Options:

    • formatName: Controls how the registration name is derived (e.g., 'camelCase').
    • resolverOptions: Applies settings like lifetime, register (e.g., asClass, asValue), and injectionMode to all loaded modules.
    const awilix = require('awilix')
    const container = awilix.createContainer()
    
    container.loadModules(
      [
        [
          'models/**/*.js',
          {
            register: awilix.asValue,
            lifetime: Lifetime.SINGLETON,
          },
        ],
        'services/**/*.js',
        'repositories/**/*.js',
      ],
      {
        formatName: 'camelCase',
        resolverOptions: {
          lifetime: Lifetime.SINGLETON,
          register: awilix.asClass,
        },
      },
    )
  9. Use Awilix in browser environments

    master

    Awilix supports browser environments via several builds. Modern bundlers like Webpack, Rollup, and Browserify will automatically select the correct version via package.json fields.

    Limitations in the browser:

    • The methods loadModules and listModules are not supported in browser builds because they rely on Node-specific packages.

    Browser Compatibility: Awilix requires Proxy and Reflect support. Supported browsers include:

    • Chrome >= 49
    • Firefox >= 18
    • Edge >= 12
    • Opera >= 36
    • Safari >= 10
    • Note: Internet Explorer is not supported.

    Available Builds:

    • CommonJS: lib/awilix.js
    • ES Modules (Node): lib/awilix.module.mjs
    • ES Modules (Browser): lib/awilix.browser.mjs
    • UMD (Script tag): lib/awilix.umd.js
  10. Create an Awilix container

    master

    Use createContainer to initialize a new dependency injection container. You can configure the container's injectionMode (defaults to PROXY) and strict mode (defaults to false).

    In strict mode, Awilix performs additional validation, such as preventing the registration of SINGLETON lifetimes on scoped containers and checking for lifetime leakage (where a shorter-lived dependency is resolved through a longer-lived ancestor).

  11. Register dependencies with container.register()

    master

    Register modules using container.register(). You can use several syntaxes:

    1. Name-Resolver pair: container.register('name', resolver)
    2. Object syntax: container.register({ name: resolver })
    3. Chaining: register returns the container, allowing you to chain multiple registrations.

    Resolvers:

    • asValue(value): Resolves the given value as-is.
    • asFunction(fn): Resolves by invoking the function with the container cradle as the first and only argument.
    • asClass(cls): Like asFunction, but uses new to instantiate the class.

    Lifetime Management: Resolvers support fluid methods to set lifetimes:

    • .singleton()
    • .scoped()
    • .transient()