inversify vs tsyringe vs injection-js vs awilix vs bottlejs
JavaScript Dependency Injection Containers for Frontend Applications
inversifytsyringeinjection-jsawilixbottlejsSimilar Packages:
JavaScript Dependency Injection Containers for Frontend Applications

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.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
inversify1,820,62210063 kB1225 days agoMIT
tsyringe1,196,0985,775149 kB757 months agoMIT
injection-js987,2781,355334 kB4a month agoMIT
awilix291,8673,977311 kB28 months agoMIT
bottlejs25,6201,300417 kB3-MIT

JavaScript Dependency Injection Containers Compared: Awilix, BottleJS, Injection-JS, Inversify, and Tsyringe

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.

🧪 Core Philosophy: How Each Container Thinks About Dependencies

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);

⚙️ TypeScript Integration: Decorators vs. Explicit Registration

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.

🔄 Lifecycle Management: Singleton, Transient, Scoped?

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.

📦 Runtime Behavior and Performance

  • 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.

🛠️ Real-World Trade-Offs: When to Use Which

Use awilix if:

  • You’re in a Node.js or isomorphic environment.
  • You want high performance and explicit control.
  • You need request-scoped dependencies (e.g., for Express middleware).
  • You prefer avoiding decorators and build-time transforms.

Use bottlejs if:

  • You’re targeting older browsers or constrained environments.
  • Your app is small-to-medium and doesn’t justify a heavy DI system.
  • You want zero dependencies and maximum compatibility.

Avoid injection-js:

  • It’s deprecated, unmaintained, and incompatible with modern TypeScript.
  • Use tsyringe or inversify instead if you liked its Angular-like style.

Use inversify if:

  • You’re building a large enterprise app with complex binding rules.
  • You need features like middleware, custom resolvers, or contextual injection.
  • Your team is comfortable managing reflect-metadata and DI planning concepts.

Use tsyringe if:

  • You want decorator-based DI with minimal setup.
  • You’re using modern TypeScript and bundlers (Vite, Webpack 5, etc.).
  • You value simplicity over advanced scoping or binding logic.

🔁 Key Similarities Across All Libraries

Despite their differences, all these containers share common ground:

1. Support for Constructor Injection

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) {}
}

2. Testability Through Mocking

Each enables easy unit testing by allowing you to override dependencies during tests.

// Example: overriding in awilix
testContainer.register({
  api: asValue(mockApi)
});

3. Modular Architecture Enablement

They all help decouple modules, making it easier to swap implementations (e.g., real vs. fake auth service).

4. Type Safety (in TypeScript variants)

awilix, inversify, and tsyringe provide strong typing for registered and resolved services, reducing runtime errors.

5. No Global State by Default

All encourage creating and passing containers explicitly, avoiding hidden global registries (though misuse can still lead to globals).

📊 Summary: Decision Matrix

Featureawilixbottlejsinjection-jsinversifytsyringe
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 SizeSmallTinyMediumLargeSmall
Best ForNode.js, APIsLightweight appsEnterprise appsModern TS apps

💡 Final Recommendation

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.

How to Choose: inversify vs tsyringe vs injection-js vs awilix vs bottlejs
  • inversify:

    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.

  • tsyringe:

    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.

  • injection-js:

    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.

  • awilix:

    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.

  • bottlejs:

    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.

README for inversify

NPM version NPM Downloads Docs Codecov

GitHub stars Discord Server

Inversify social

📕 Documentation

Documentation is available at https://inversify.io

About

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.

Motivation

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.

Philosophy

InversifyJS has been developed with 4 main goals:

  1. Allow JavaScript developers to write code that adheres to the SOLID principles.

  2. Facilitate and encourage the adherence to the best OOP and IoC practices.

  3. Add as little runtime overhead as possible.

  4. Provide a state of the art development experience.

Testimonies

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

Some companies using InversifyJS

Acknowledgements

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.