Awilix

Extremely powerful Inversion of Control (IoC) container for Node.JS

README

Awilix


Extremely powerful, performant, & battle-tested Dependency Injection (DI) container for JavaScript/Node,
written in TypeScript.

Awilix enables you to write composable, testable software using dependency injection without special annotations, which in turn decouples your core application code from the intricacies of the DI mechanism.

💡 Check out this


Installation


Install with npm

  1. ```
  2. npm install awilix
  3. ```

Or yarn

  1. ```
  2. yarn add awilix
  3. ```

You can also use the UMD build fromunpkg

  1. ```html
  2. <script src="https://unpkg.com/awilix/lib/awilix.umd.js"/>
  3. <script>
  4. const container = Awilix.createContainer()
  5. </script>
  6. ```

Usage


Awilix has a pretty simple API (but with many possible ways to invoke it). At
minimum, you need to do 3 things:

- Create a container
- Register some modules in it
- Resolve and use!

index.js

  1. ```javascript
  2. const awilix = require('awilix')

  3. // Create the container and set the injectionMode to PROXY (which is also the default).
  4. const container = awilix.createContainer({
  5.   injectionMode: awilix.InjectionMode.PROXY
  6. })

  7. // This is our app code... We can use
  8. // factory functions, constructor functions
  9. // and classes freely.
  10. class UserController {
  11.   // We are using constructor injection.
  12.   constructor(opts) {
  13.     // Save a reference to our dependency.
  14.     this.userService = opts.userService
  15.   }

  16.   // imagine ctx is our HTTP request context...
  17.   getUser(ctx) {
  18.     return this.userService.getUser(ctx.params.id)
  19.   }
  20. }

  21. container.register({
  22.   // Here we are telling Awilix how to resolve a
  23.   // userController: by instantiating a class.
  24.   userController: awilix.asClass(UserController)
  25. })

  26. // Let's try with a factory function.
  27. const makeUserService = ({ db }) => {
  28.   // Notice how we can use destructuring
  29.   // to access dependencies
  30.   return {
  31.     getUser: id => {
  32.       return db.query(`select * from users where id=${id}`)
  33.     }
  34.   }
  35. }

  36. container.register({
  37.   // the `userService` is resolved by
  38.   // invoking the function.
  39.   userService: awilix.asFunction(makeUserService)
  40. })

  41. // Alright, now we need a database.
  42. // Let's make that a constructor function.
  43. // Notice how the dependency is referenced by name
  44. // directly instead of destructuring an object.
  45. // This is because we register it in "CLASSIC"
  46. // injection mode below.
  47. function Database(connectionString, timeout) {
  48.   // We can inject plain values as well!
  49.   this.conn = connectToYourDatabaseSomehow(connectionString, timeout)
  50. }

  51. Database.prototype.query = function(sql) {
  52.   // blah....
  53.   return this.conn.rawSql(sql)
  54. }

  55. // We use register coupled with asClass to tell Awilix to
  56. // use `new Database(...)` instead of just `Database(...)`.
  57. // We also want to use `CLASSIC` injection mode for this
  58. // registration. Read more about injection modes below.
  59. container.register({
  60.   db: awilix.asClass(Database).classic()
  61. })

  62. // Lastly we register the connection string and timeout values
  63. // as we need them in the Database constructor.
  64. container.register({
  65.   // We can register things as-is - this is not just
  66.   // limited to strings and numbers, it can be anything,
  67.   // really - they will be passed through directly.
  68.   connectionString: awilix.asValue(process.env.CONN_STR),
  69.   timeout: awilix.asValue(1000)
  70. })

  71. // We have now wired everything up!
  72. // Let's use it! (use your imagination with the router thing..)
  73. router.get('/api/users/:id', container.resolve('userController').getUser)

  74. // Alternatively, using the `cradle` proxy..
  75. router.get('/api/users/:id', container.cradle.userController.getUser)

  76. // Using  `container.cradle.userController` is actually the same as calling
  77. // `container.resolve('userController')` - the cradle is our proxy!
  78. ```

That example is rather lengthy, but if you extract things to their proper files
it becomes more manageable.


Lifetime management


Awilix supports managing the lifetime of instances. This means that you can
control whether objects are resolved and used once, cached within a certain
scope, or cached for the lifetime of the process.

There are 3 lifetime types available.

- Lifetime.TRANSIENT: This is the default. The registration is resolved every
  time it is needed. This means if you resolve a class more than once, you will
  get back a new instance every time.
- Lifetime.SCOPED: The registration is scoped to the container - that means
  that the resolved value will be reused when resolved from the same scope (or a
  child scope).
- Lifetime.SINGLETON: The registration is always reused no matter what - that
  means that the resolved value is cached in the root container.

They are exposed on the awilix.Lifetime object.

  1. ```js
  2. const Lifetime = awilix.Lifetime
  3. ```

To register a module with a specific lifetime:

  1. ```js
  2. const { asClass, asFunction, asValue } = awilix

  3. class MailService {}

  4. container.register({
  5.   mailService: asClass(MailService, { lifetime: Lifetime.SINGLETON })
  6. })

  7. // or using the chaining configuration API..
  8. container.register({
  9.   mailService: asClass(MailService).setLifetime(Lifetime.SINGLETON)
  10. })

  11. // or..
  12. container.register({
  13.   mailService: asClass(MailService).singleton()
  14. })

  15. // or.......
  16. container.register('mailService', asClass(MailService, { lifetime: SINGLETON }))
  17. ```

Scoped lifetime


In web applications, managing state without depending too much on the web
framework can get difficult. Having to pass tons of information into every
function just to make the right choices based on the authenticated user.

Scoped lifetime in Awilix makes this simple - and fun!

  1. ```js
  2. const { createContainer, asClass, asValue } = awilix
  3. const container = createContainer()

  4. class MessageService {
  5.   constructor({ currentUser }) {
  6.     this.user = currentUser
  7.   }

  8.   getMessages() {
  9.     const id = this.user.id
  10.     // wee!
  11.   }
  12. }

  13. container.register({
  14.   messageService: asClass(MessageService).scoped()
  15. })

  16. // imagine middleware in some web framework..
  17. app.use((req, res, next) => {
  18.   // create a scoped container
  19.   req.scope = container.createScope()

  20.   // register some request-specific data..
  21.   req.scope.register({
  22.     currentUser: asValue(req.user)
  23.   })

  24.   next()
  25. })

  26. app.get('/messages', (req, res) => {
  27.   // for each request we get a new message service!
  28.   const messageService = req.scope.resolve('messageService')
  29.   messageService.getMessages().then(messages => {
  30.     res.send(200, messages)
  31.   })
  32. })

  33. // The message service can now be tested
  34. // without depending on any request data!
  35. ```

IMPORTANT! If a singleton is resolved, and it depends on a scoped or
transient registration, those will remain in the singleton for it's lifetime!

  1. ```js
  2. const makePrintTime = ({ time }) => () => {
  3.   console.log('Time:', time)
  4. }

  5. const getTime = () => new Date().toString()

  6. container.register({
  7.   printTime: asFunction(makePrintTime).singleton(),
  8.   time: asFunction(getTime).transient()
  9. })

  10. // Resolving `time` 2 times will
  11. // invoke `getTime` 2 times.
  12. container.resolve('time')
  13. container.resolve('time')

  14. // These will print the same timestamp at all times,
  15. // because `printTime` is singleton and
  16. // `getTime` was invoked when making the singleton.
  17. container.resolve('printTime')()
  18. container.resolve('printTime')()
  19. ```

Read the documentation for [container.createScope()](#containercreatescope)
for more examples.

Injection modes


The injection mode determines how a function/constructor receives its
dependencies. Pre-2.3.0, only one mode was supported - PROXY - which remains
the default mode.

Awilix v2.3.0 introduced an alternative injection mode: CLASSIC. The injection
modes are available on awilix.InjectionMode

- InjectionMode.PROXY (default): Injects a proxy to functions/constructors
  which looks like a regular object.

  js
  class UserService {
    constructor(opts) {
      this.emailService = opts.emailService
      this.logger = opts.logger
    }
  }
  

  or with destructuring:

  js
  class UserService {
    constructor({ emailService, logger }) {
      this.emailService = emailService
      this.logger = logger
    }
  }
  

- InjectionMode.CLASSIC: Parses the function/constructor parameters, and
  matches them with registrations in the container. CLASSIC mode has a
  slightly higher initialization cost as it has to parse the function/class
  to figure out the dependencies at the time of registration, however resolving
  them will be much faster than when using PROXY. _Don't use CLASSIC if
  you minify your code!_ We recommend using CLASSIC in Node and PROXY in
  environments where minification is needed.

  js
  class UserService {
    constructor(emailService, logger) {
      this.emailService = emailService
      this.logger = logger
    }
  }
  

  Additionally, if the class has a base class but does not declare a constructor of its own, Awilix
  simply invokes the base constructor with whatever dependencies it requires.

  js
  class Car {
    constructor(engine) {
      this.engine = engine
    }
  }

  class Porsche extends Car {
    vroom() {
      console.log(this.engine) // whatever "engine" is
    }
  }