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 intro to Dependency Injection with Awilix
awilix objectcreateContainer()InferCradleFromResolversInferCradleFromContainerasFunction()asClass()asValue()aliasTo()listModules()AwilixResolutionErrorAwilixRegistrationErrorAwilixContainer object
Install with npm
npm install awilix
Or yarn
yarn add awilix
You can also use the UMD build from unpkg
<script src="https://unpkg.com/awilix/lib/awilix.umd.js" />
<script>
const container = Awilix.createContainer()
</script>
Awilix has a pretty simple API (but with many possible ways to invoke it). At minimum, you need to do 3 things:
index.js
const awilix = require('awilix')
// Create the container and set the injectionMode to PROXY (which is also the default).
// Enable strict mode for extra correctness checks (highly recommended).
const container = awilix.createContainer({
injectionMode: awilix.InjectionMode.PROXY,
strict: true,
})
// This is our app code... We can use
// factory functions, constructor functions
// and classes freely.
class UserController {
// We are using constructor injection.
constructor(opts) {
// Save a reference to our dependency.
this.userService = opts.userService
}
// imagine ctx is our HTTP request context...
getUser(ctx) {
return this.userService.getUser(ctx.params.id)
}
}
container.register({
// Here we are telling Awilix how to resolve a
// userController: by instantiating a class.
userController: awilix.asClass(UserController),
})
// Let's try with a factory function.
const makeUserService = ({ db }) => {
// Notice how we can use destructuring
// to access dependencies
return {
getUser: (id) => {
return db.query(`select * from users where id=${id}`)
},
}
}
container.register({
// the `userService` is resolved by
// invoking the function.
userService: awilix.asFunction(makeUserService),
})
// Alright, now we need a database.
// Let's make that a constructor function.
// Notice how the dependency is referenced by name
// directly instead of destructuring an object.
// This is because we register it in "CLASSIC"
// injection mode below.
function Database(connectionString, timeout) {
// We can inject plain values as well!
this.conn = connectToYourDatabaseSomehow(connectionString, timeout)
}
Database.prototype.query = function (sql) {
// blah....
return this.conn.rawSql(sql)
}
// We use register coupled with asClass to tell Awilix to
// use `new Database(...)` instead of just `Database(...)`.
// We also want to use `CLASSIC` injection mode for this
// registration. Read more about injection modes below.
container.register({
db: awilix.asClass(Database).classic(),
})
// Lastly we register the connection string and timeout values
// as we need them in the Database constructor.
container.register({
// We can register things as-is - this is not just
// limited to strings and numbers, it can be anything,
// really - they will be passed through directly.
connectionString: awilix.asValue(process.env.CONN_STR),
timeout: awilix.asValue(1000),
})
// We have now wired everything up!
// Let's use it! (use your imagination with the router thing..)
router.get('/api/users/:id', container.resolve('userController').getUser)
// Alternatively, using the `cradle` proxy..
router.get('/api/users/:id', container.cradle.userController.getUser)
// Using `container.cradle.userController` is actually the same as calling
// `container.resolve('userController')` - the cradle is our proxy!
That example is rather lengthy, but if you extract things to their proper files it becomes more manageable.
Check out a working Koa example!
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.
const Lifetime = awilix.Lifetime
To register a module with a specific lifetime:
const { asClass, asFunction, asValue } = awilix
class MailService {}
container.register({
mailService: asClass(MailService, { lifetime: Lifetime.SINGLETON }),
})
// or using the chaining configuration API..
container.register({
mailService: asClass(MailService).setLifetime(Lifetime.SINGLETON),
})
// or..
container.register({
mailService: asClass(MailService).singleton(),
})
// or.......
container.register('mailService', asClass(MailService, { lifetime: SINGLETON }))
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!
const { createContainer, asClass, asValue } = awilix
const container = createContainer()
class MessageService {
constructor({ currentUser }) {
this.user = currentUser
}
getMessages() {
const id = this.user.id
// wee!
}
}
container.register({
messageService: asClass(MessageService).scoped(),
})
// imagine middleware in some web framework..
app.use((req, res, next) => {
// create a scoped container
req.scope = container.createScope()
// register some request-specific data..
req.scope.register({
currentUser: asValue(req.user),
})
next()
})
app.get('/messages', (req, res) => {
// for each request we get a new message service!
const messageService = req.scope.resolve('messageService')
messageService.getMessages().then((messages) => {
res.send(200, messages)
})
})
// The message service can now be tested
// without depending on any request data!
IMPORTANT! If a singleton is resolved, and it depends on a scoped or
transient registration, those will remain in the singleton for its lifetime!
Similarly, if a scoped module is resolved, and it depends on a transient
registration, that remains in the scoped module for its lifetime.
In the example above, if messageService was a singleton, it would be cached
in the root container, and would always have the currentUser from the first
request. Modules should generally not have a longer lifetime than their
dependencies, as this can cause issues of stale data.
const makePrintTime =
({ time }) =>
() => {
console.log('Time:', time)
}
const getTime = () => new Date().toString()
container.register({
printTime: asFunction(makePrintTime).singleton(),
time: asFunction(getTime).transient(),
})
// Resolving `time` 2 times will
// invoke `getTime` 2 times.
container.resolve('time')
container.resolve('time')
// These will print the same timestamp at all times,
// because `printTime` is singleton and
// `getTime` was invoked when making the singleton.
container.resolve('printTime')()
container.resolve('printTime')()
If you want a mismatched configuration like this to error, set
strict in the container options. This will trigger
the following error at runtime when the singleton printTime is resolved:
AwilixResolutionError: Could not resolve 'time'. Dependency 'time' has a shorter lifetime than its ancestor: 'printTime'
In addition, registering a singleton on a scope other than the root container results in unpredictable behavior. In particular, if two different singletons are registered on two different scopes, they will share a cache entry and collide with each other. To throw a runtime error when a singleton is registered on a scope other than the root container, enable strict mode.
Read the documentation for container.createScope()
for more examples.
Strict mode is a new feature in Awilix 10. It enables additional correctness checks that can help you catch bugs early.
In particular, strict mode enables the following checks:
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 exten
$ claude mcp add awilix \
-- python -m otcore.mcp_server <graph>