awilix, bottlejs, injection-js, inversify, and tsyringe are all dependency injection (DI) containers designed to manage object creation, lifecycle, and dependencies in JavaScript and TypeScript applications. They enable inversion of control by decoupling class instantiation from usage, promoting testability, modularity, and maintainability. While all serve the same core purpose, they differ significantly in API design, TypeScript integration, runtime behavior, and architectural assumptions.
Dependency injection (DI) isn’t just for Java or C# — it’s a powerful pattern in JavaScript too, especially as frontend apps grow in complexity. These five libraries all aim to solve the same problem: managing how objects get created and wired together. But they do it in very different ways. Let’s break down what really matters when choosing one.
awilix treats DI as a registry of resolvers — functions that know how to build things. It doesn’t care if you use classes or plain functions. Registration is explicit and usually done via code (not decorators), making it predictable and debuggable.
// awilix: register with a factory function
container.register({
logger: asFunction(createLogger),
db: asClass(Database).singleton()
});
const logger = container.resolve('logger');
bottlejs uses a fluent, method-chaining API that feels like configuration-as-code. Everything is defined through method calls like .service() or .factory(). No decorators, no metadata — just plain JavaScript.
// bottlejs: define services imperatively
bottle.service('Cache', CacheService)
.factory('Api', (container) => new ApiService(container.Cache));
const api = bottle.container.Api;
injection-js was built to mimic Angular’s DI system using decorators and reflection. However, it’s deprecated and hasn’t been updated in years. It requires reflect-metadata and complex setup, and lacks modern TypeScript support. Do not use it in new projects.
inversify brings full-featured, enterprise-style DI to TypeScript. It uses decorators (@injectable, @inject) combined with runtime metadata to resolve complex dependency graphs. It supports advanced scenarios like binding based on context or custom tags.
// inversify: decorator-based with symbolic identifiers
@injectable()
class Ninja {
constructor(@inject('Katana') private katana: Katana) {}
}
container.bind<Ninja>('Ninja').to(Ninja);
tsyringe also uses decorators but keeps things lean. It relies only on TypeScript’s built-in decorator metadata (no reflect-metadata polyfill needed) and uses simple tokens like classes or strings. It’s designed to “just work” with modern tooling.
// tsyringe: minimal decorators
@injectable()
class HttpClient {}
const client = container.resolve(HttpClient);
If you’re using TypeScript, this is a major decision point.
inversify and tsyringe require decorators. But inversify needs the reflect-metadata shim and specific tsconfig.json settings (emitDecoratorMetadata: true, experimentalDecorators: true). tsyringe works with standard decorators and doesn’t need the metadata shim, making it easier to set up.
awilix and bottlejs don’t use decorators at all. You register dependencies manually. This means no build-time magic, fewer surprises, and better compatibility with strict bundler configurations (like Vite or esbuild).
injection-js requires decorators and reflect-metadata, but since it’s unmaintained, type definitions are outdated and may break with newer TypeScript versions.
All libraries support singleton and transient lifecycles, but scoping differs:
awilix shines here with first-class support for scoped containers. You can create a child container per HTTP request (in Node.js) or per user session, ensuring isolated state without global pollution.const scope = container.createScope();
scope.register({ userSession: asValue(sessionData) });
inversify supports scoping via inRequestScope(), but it’s tied to its internal planning system and can be tricky to use outside of web servers.
tsyringe only supports singleton and transient out of the box. Scoping requires manual container cloning or external management.
bottlejs has no built-in scoping — everything is effectively singleton unless you use factories that return new instances each time.
awilix is optimized for speed. Resolving dependencies is essentially a map lookup plus function call. No reflection, no metadata parsing at runtime.
inversify incurs overhead during resolution because it walks a “plan” tree built from metadata. This enables powerful features but adds latency on first resolve.
tsyringe caches resolved constructors but still reads metadata on first access. Faster than inversify but slower than awilix or bottlejs.
bottlejs is extremely lightweight — under 2KB minified. Resolution is direct property access or function invocation.
awilix if:bottlejs if:injection-js:tsyringe or inversify instead if you liked its Angular-like style.inversify if:reflect-metadata and DI planning concepts.tsyringe if:Despite their differences, all these containers share common ground:
All allow injecting dependencies via class constructors — the most common DI pattern in object-oriented design.
// Works in awilix (via proxy), inversify, tsyringe
class Service {
constructor(private dep: Dependency) {}
}
Each enables easy unit testing by allowing you to override dependencies during tests.
// Example: overriding in awilix
testContainer.register({
api: asValue(mockApi)
});
They all help decouple modules, making it easier to swap implementations (e.g., real vs. fake auth service).
awilix, inversify, and tsyringe provide strong typing for registered and resolved services, reducing runtime errors.
All encourage creating and passing containers explicitly, avoiding hidden global registries (though misuse can still lead to globals).
| Feature | awilix | bottlejs | injection-js | inversify | tsyringe |
|---|---|---|---|---|---|
| Active Maintenance | ✅ Yes | ✅ Yes | ❌ No (deprecated) | ✅ Yes | ✅ Yes |
| Decorator-Based | ❌ No | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes |
| Requires reflect-metadata | ❌ No | ❌ No | ✅ Yes | ✅ Yes | ❌ No |
| Scoped Containers | ✅ Excellent | ❌ No | ⚠️ Limited | ✅ Yes (complex) | ❌ No |
| Bundle Size | Small | Tiny | Medium | Large | Small |
| Best For | Node.js, APIs | Lightweight apps | — | Enterprise apps | Modern TS apps |
For most modern frontend or full-stack TypeScript projects, tsyringe offers the best balance: simple decorators, no extra polyfills, and solid typing. If you’re in Node.js and need scoping, go with awilix. If you’re building a large system with complex binding rules, inversify is worth the overhead. Use bottlejs only if you’re in a legacy or ultra-lightweight context. And never start a new project with injection-js — it’s a relic.
Choose inversify if you're building a large-scale TypeScript application that benefits from a rich, enterprise-grade DI system with advanced features like contextual bindings, middleware, and planning hooks. Be prepared to configure your build pipeline for reflect-metadata and accept its steeper learning curve and runtime overhead. It’s best suited for complex architectures where fine-grained control over dependency resolution is critical.
Choose tsyringe if you want a lightweight, decorator-based DI container that integrates seamlessly with TypeScript using only standard decorators (no reflect-metadata required). It’s ideal for frontend or full-stack apps that value simplicity, fast startup time, and compatibility with modern bundlers. Avoid it if you need advanced scoping beyond singleton/transient or require non-decorator registration patterns.
Do not choose injection-js for new projects. It is a deprecated fork of an early Angular DI implementation and has been unmaintained for years. While it introduced decorator-based DI to the ecosystem, modern alternatives like tsyringe or inversify offer better TypeScript integration, active maintenance, and more robust features.
Choose awilix if you're building a Node.js or isomorphic application that values performance, minimal overhead, and clean functional-style registration. It works well with both classes and factory functions, supports scoped containers for request-level isolation, and offers excellent TypeScript support without requiring decorators. Avoid it if you strictly require decorator-based metadata like Angular or .NET DI systems.
Choose bottlejs if you need a lightweight, zero-dependency DI container that works in any JavaScript environment—including older browsers—and prefer a fluent, chainable API without TypeScript decorators. Its simplicity makes it ideal for small to medium apps where you want explicit control over service definitions without build-time transformations or reflection.

Documentation is available at https://inversify.io
InversifyJS is a lightweight inversion of control (IoC) container for TypeScript and JavaScript apps. An IoC container uses a class constructor to identify and inject its dependencies. InversifyJS has a friendly API and encourages the usage of the best OOP and IoC practices.
JavaScript now supports object oriented (OO) programming with class based inheritance. These features are great but the truth is that they are also dangerous.
We need a good OO design (SOLID, Composite Reuse, etc.) to protect ourselves from these threats. The problem is that OO design is difficult and that is exactly why we created InversifyJS.
InversifyJS is a tool that helps JavaScript developers write code with good OO design.
InversifyJS has been developed with 4 main goals:
Allow JavaScript developers to write code that adheres to the SOLID principles.
Facilitate and encourage the adherence to the best OOP and IoC practices.
Add as little runtime overhead as possible.
Provide a state of the art development experience.
Nate Kohari - Author of Ninject
"Nice work! I've taken a couple shots at creating DI frameworks for JavaScript and TypeScript, but the lack of RTTI really hinders things. The ES7 metadata gets us part of the way there (as you've discovered). Keep up the great work!"
Michel Weststrate - Author of MobX
Dependency injection like InversifyJS works nicely
Thanks a lot to all the contributors, all the developers out there using InversifyJS and all those that help us to spread the word by sharing content about InversifyJS online. Without your feedback and support this project would not be possible.