Awilix: dependency injection for Node without annotations
Extremely powerful Inversion of Control (IoC) container for Node.JS
At a glance
- What is it?
- A TypeScript dependency injection container that resolves classes, factory functions and plain values through one registration object, with proxy and classic injection modes and a real disposal story.
- Who is it for?
- Awilix's distinguishing idea is that resolution should be ordinary JavaScript rather than a decorator system. A registration names how to build something, and the cradle hands your functions a proxy that resolves dependencies as they are destructured, so application code never imports a container and stays trivially testable.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 23 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The three things you have to do
Awilix is a dependency injection container for JavaScript and Node, written in TypeScript, and the README keeps the mental model small. At minimum you do three things: create a container, register modules in it, then resolve and use. That framing matters because most DI libraries in other ecosystems make step two a ceremony of annotations or attribute scanning. The README's stated advantage of Awilix is that you can write composable, testable software with dependency injection without special annotations, which in turn keeps your core application code clear of the DI mechanism.
Installation is the usual three routes, npm, yarn, or a UMD build from unpkg for a browser:
npm install awilixThe package metadata is more specific about what you are installing. Version 13.0.5, MIT licensed, with `main` pointing at a CommonJS build, a `module` entry for ES modules, a separate `browser` build, a UMD build, and typings. The engines field requires Node 20 or newer, which is a meaningful floor for a container people often reach for in older services. The exports map resolves separate entry points for `browser`, `react-native` and `workerd`, so the same package can be imported by a bundler, by React Native, or by a worker runtime without you aliasing paths yourself.
At 4230 stars, 148 forks and 5 open issues, with the last push on 2026-09-13, this is an established library rather than an experiment.
Resolvers are how you say what a thing is
The registration API is built around four resolver factories, and the README's main example uses three of them. `asClass` means construct with `new`, `asFunction` means invoke as a factory, `asValue` means pass through as-is, and `aliasTo` points one name at another.
The example registers a controller class, a service factory, a database constructor and two plain values, and it is worth reading closely because it shows the two styles of dependency access in one file:
container.register({
userController: awilix.asClass(UserController),
})A factory function destructures its dependencies from a single argument, so the function declares what it needs by naming what it wants. A classic registration takes them as ordinary positional parameters instead, which is why the README registers the database with `.classic()` and then reaches for `connectionString` and `timeout` by name. Both styles can live in the same container.
Plain values go through the same door, which is the detail that keeps test doubles honest:
container.register({
connectionString: awilix.asValue(process.env.CONN_STR),
timeout: awilix.asValue(1000),
})Because a value is registered the same way as a class, overriding it in a test is a registration change rather than a different construction path.
Proxy mode and why it makes code testable
Container creation takes options, and the README recommends two of them: `injectionMode: awilix.InjectionMode.PROXY`, which is also the default, and `strict: true`, described as highly recommended for extra correctness checks.
The proxy mode is the heart of the library. In it, the object your function receives is a proxy that resolves each property the moment it is accessed, so destructuring `db` out of the argument triggers resolution of `db` and nothing else. Resolving the whole graph up front is what proxy mode avoids, and that is why a request-scoped database does not get constructed for a code path that never touched it.
Consumption then happens in two interchangeable forms, which the README shows as equivalent:
router.get('/api/users/:id', container.resolve('userController').getUser)
router.get('/api/users/:id', container.cradle.userController.getUser)The cradle is the proxy, so `container.cradle.userController` and `container.resolve('userController')` produce the same instance. Strict mode is what makes a typo in a dependency name fail loudly at resolution time rather than quietly becoming undefined, which is the failure mode that costs the most time in an untested service.
The metadata description calls the project an inversion of control container for Node, and the README calls it battle-tested. The repository backs the second word differently: there is a `benchmarks/` directory with a script to run them all, so performance claims are at least something you can go and measure.
Lifetimes, scopes and disposal
The README's feature list moves on to lifetime management after the usage example, which is the correct order of importance for a container that will be resolving per-request dependencies in a server.
Awilix gives you the standard three choices plus scopes. Transient lifetimes resolve fresh every time, singletons are cached for the container's life, and a scoped lifetime sits between them, created once per scope rather than once per process. That third option is what a web framework wants, where each request should get its own database handle or unit of work and should not share it with the next request.
Scopes are created explicitly with `container.createScope()`, and the container exposes `dispose()`, which pairs with the README's disposing section. Disposal is the part many containers handle poorly, and having it in the API means resources with a teardown step can be released deterministically rather than left to process exit.
The rest of the documented surface, taken from the README's own table of contents, covers strict mode, injection modes, auto-loading modules, per-module local injections, inlining resolver options, a full API reference down to `AwilixResolutionError` and `AwilixRegistrationError`, a universal module build for browser support, an ecosystem section, and a page on migrating from older versions. That last heading is worth pausing on for a library at version 13, since it implies the API has changed across majors and the migration notes are part of the README rather than a blog post.
Examples, tooling and the release picture
The repository ships four runnable examples, which for a library like this is more informative than documentation prose. There is a simple example, a Koa example the README links to directly, a TypeScript example, and a Babel example. Koa is the one worth reading if you are evaluating the library, because scoped lifetimes only make sense once you see them wired to a request.
Development tooling is conventional and visible in the package scripts. The build removes `lib` and runs TypeScript compilation followed by rollup, checks run `tsc --noEmit`, tests are jest and the test script runs the type check first so a type error fails the suite, and there are separate lint, format, coverage and benchmark scripts. Publishing is gated by a pre-publish script that lints, builds and runs coverage before anything goes out, and hooks live in `.husky/`.
One discrepancy is worth naming rather than smoothing over. `package.json` reports version 13.0.5, and the project carries a CHANGELOG.md, yet GitHub reports no published releases for this repository, so there are no tagged release notes to read on the releases page. Both facts are true. The practical consequence is that version history for this library lives in the changelog file and on npm rather than in GitHub releases, and the badge in the README points at npm for the current version. If you are auditing what changed between versions, the changelog is the place to look.
The examples also tell you what Awilix does not try to be. There is no framework integration here, no decorator support, and no attempt to guess your module layout. You register what you want resolved.
Editorial conclusion
Awilix's distinguishing idea is that resolution should be ordinary JavaScript rather than a decorator system. A registration names how to build something, and the cradle hands your functions a proxy that resolves dependencies as they are destructured, so application code never imports a container and stays trivially testable. What the README promises and what the repository shows are mostly in agreement; the notable gap is release history, since the npm package is at 13.0.5 while GitHub reports no published release tags, so changelog detail lives in the repository's CHANGELOG.md rather than on the releases page. Start with one container, strict mode on, and a handful of `asClass` registrations. Add `asFunction` for anything you build rather than construct, and reach for scoped containers only once you hit a request that must not share state.
Frequently asked questions
What is the purpose of Awilix?
Awilix is a dependency injection container for JavaScript and Node, written in TypeScript. Its stated purpose is to let you write composable, testable software with dependency injection but without special annotations, so your application code stays decoupled from the DI mechanism itself. The GitHub project description frames the same tool as an inversion of control container for Node.
What is the difference between proxy and classic injection mode?
In proxy mode, the default, the injected object is a proxy that resolves each dependency as you destructure it, so only what you actually access gets constructed. Classic mode, enabled per registration with `.classic()`, passes dependencies as ordinary positional parameters instead. The README's main example registers a controller and a service in proxy style and a database in classic style, in the same container.
Which Node.js versions does Awilix support?
The package metadata sets the engines field to Node 20 or newer for the current version, 13.0.5. Separate exports resolve for browser, react-native and workerd targets, so the same install works in a bundler, a React Native app or a worker runtime.
How do I dispose of resolved instances in Awilix?
The container exposes a `dispose()` method, and the README has a section devoted to disposing. Scoped lifetimes come from `container.createScope()`, so a scope is the natural unit to create per request and dispose at the end of it, which is how you get deterministic teardown for resources that hold connections.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jeffijoe-awilix)