Comparison
How injecute compares with other ways to do dependency injection in TypeScript. Every claim about another library links to its docs, README or source in Sources, checked against the versions in the first table. Found something wrong or out of date? Tell us in Discussions.
Wiring
| Library | Version | How a service gets its dependencies | A missing registration is… | Decorators / reflect-metadata |
|---|---|---|---|---|
| injecute | 1.0 | Each registration lists its dependency keys: addSingleton('users', Repo, ['db']) | a compile error ("Did you mean …?") | none |
| typed-inject | 5.0.0 | A static inject = ['db'] as const array on the class or function | a compile error | none |
| needle-di | 1.2.1 | inject(Token) in constructor defaults or fields, or providers bound by hand | a runtime error (token types are checked) | optional standard decorators, no reflect-metadata |
| Awilix | 13.0.5 | By name: a destructured "cradle" object, or parsed parameter names; loadModules globs | a runtime error (the container type grows since v13, asClass isn't typed) | none |
| InversifyJS | 8.2.3 | @injectable() / @inject(id) and container.bind(id) | a runtime error (opt-in typed binding map) | required for decorator injection |
| tsyringe | 4.10.0 | @injectable() and emitted constructor types; @inject(token) for other tokens | a runtime error | required |
| NestJS | 12.3.2 | @Injectable() providers in modules, resolved by constructor type | a runtime error at bootstrap | required |
| Effect | 4.1.1 | Services (Context.Service) used with yield*, provided by layers | a compile error (unmet requirements stay in the type) | none |
Lifetimes, async and disposal
| Library | Scoped services | A singleton that depends on a scoped service | Async services | Disposal |
|---|---|---|---|---|
| injecute | Scopes: a fork with its own services | a compile error for a scope-only key; a scope that re-registers a root key must say replace: true, and the error names the root services that keep the root's instance | AsyncDIContainer awaits every dependency; async singletons in any container | dispose() / await using (Symbol.dispose), lifecycle hooks |
| typed-inject | Child injectors; Singleton and Transient | not documented | not documented | injector.dispose() calls dispose() methods |
| needle-di | Child containers; every provider is a singleton per container | not documented | async providers with getAsync() | not documented |
| Awilix | SCOPED lifetime with createScope() | a runtime error in strict mode (opt-in, off by default) | not built in (a third-party package) | dispose() with per-registration disposers |
| InversifyJS | Child containers; "Request" means one get() call | not handled | getAsync() | onDeactivation / @preDestroy on unbind; Symbol.dispose through a plugin |
| tsyringe | ContainerScoped in child containers | not handled | none | dispose() calls dispose() methods |
| NestJS | Scope.REQUEST, durable providers | prevented: a request-scoped dependency makes its consumers request-scoped too | async factory providers | lifecycle hooks (not called for request-scoped providers) |
| Effect | No named scope: provide the service or layer per unit of work | not documented | layers are built from effects | scopes and finalizers (acquireRelease) |
Size
The size of a small app that registers a value and a service that depends on it, resolves the service and prints it: bundled with esbuild 0.28 for the browser, minified and gzipped, each bundle run to check it prints the right value.
| Library | Bundle (gzipped) | Runtime dependencies | Module formats |
|---|---|---|---|
| typed-inject | 1.4 kB | none | ESM |
| needle-di | 2.7 kB | none | ESM (npm and JSR) |
| Awilix | 3.6 kB (browser build, without loadModules) | fast-glob (Node.js build) | CommonJS, ESM, browser |
| injecute | 6.3 kB (error hints are left out in browser bundles) | none | ESM (npm and JSR) |
| tsyringe | 9.2 kB with the reflect-metadata polyfill it needs | tslib, plus a Reflect polyfill | CommonJS, ES5 ESM |
| InversifyJS | 22.9 kB | @inversifyjs/* packages | ESM |
| Effect | 59.7 kB (the Effect runtime) | none | ESM |
| NestJS | not comparable: a server framework | 5 small packages; reflect-metadata, rxjs as peers | ESM |
Versions and dependencies are from the npm registry.
Where injecute is different
- Explicit scopes that can't leak. A root service can't name a key that only a scope registers, and a scope that re-registers a root key gets an error naming the root services that keep the root's instance. Of the libraries above, NestJS prevents the leak by making consumers request-scoped, Awilix detects it in strict mode, and the rest don't handle it.
- A whole-graph check in one test.
checkWiring()reports every missing key and every failing factory at once. - Errors with codes and pages. Every error has a stable code, the resolution path, and a page that says how to fix it.
- Lifecycle and disposal built in.
dispose(),await using, and startup and shutdown hooks without a framework. - Docs for coding agents in the package.
llms.txt, the docs of the installed version and an Agent Skill ship with it (AI agents).
Sources
typed-inject
- Version 5.0.0: CHANGELOG.
static injecttokens, no decorators: Usage, Motivation.- "If your project compiles, you know your dependencies are resolved at runtime": Installation.
- Singleton and Transient, child injectors: Lifecycle control, createChildInjector.
- Disposal: Disposing provided stuff.
- ESM only since v4, no dependencies: CHANGELOG.
needle-di
- Version 1.2.1: CHANGELOG.
inject()and binding: Injection, Binding.- Missing providers throw at runtime: Optional injection.
- Standard decorators, optional: Auto-binding.
- Singletons per container, child containers: Providers, Child containers.
- Async providers: Async injection.
- ESM, npm and JSR: Getting started.
Awilix
- Version 13.0.5: CHANGELOG.
- Injection by name,
PROXYandCLASSICmodes: Injection modes. - Typing:
resolveoverloads,asClass, InferCradleFromContainer. - Lifetimes and scopes: Lifetime management.
- Strict mode, off by default, and the lifetime leak error: createContainer, Strict mode.
- Async through a third-party package: Ecosystem.
- Disposal: Disposing.
- Browser build without
loadModules: Universal module.
InversifyJS
- Version 8.2.3; ESM only since v8: Migrating from v7.
- Decorators and binding: Getting started.
- Decorators and reflect-metadata required: Requirements.
- Typed binding map (opt-in): Strongly typed.
- Scopes and child containers: Binding, DI hierarchy.
- No lifetime check: the core's error kinds have none (InversifyCoreErrorKind).
- Async: Asynchronously resolved bindings.
- Disposal: Deactivation, plugin-dispose.
tsyringe
- Version 4.10.0; no code changes since that release: commits.
- Decorators, emitted types and a Reflect polyfill required: Installation.
- Untyped tokens, runtime errors: dependency-container.ts.
- Scopes and child containers: Scoped, Child containers.
- Synchronous factories only: Factory provider.
- Disposal: Disposable instances.
NestJS
- Version 12.3.2: npm.
- Providers resolved by type: Dependency injection.
- Missing providers fail at bootstrap: Common errors.
- Decorators and emitted metadata: TypeScript starter.
- Scopes, and "The
REQUESTscope bubbles up the injection chain": Injection scopes. - Async providers: Async providers.
- Lifecycle hooks: Lifecycle events.
Effect
- Version 4.1.1; v4 replaced
Context.TagandEffect.ServicewithContext.Service: services migration. - Services and the compile-time requirement check: Services.
- Layers and memoization: Layers, Layer memoization.
- Scopes and finalizers: Scope.
injecute
- Wiring and types: Design principles, TypeScript.
- Scopes: Scoped state, Overriding a parent's service in a fork.
- Async: Async services. Disposal: Lifecycle and dispose.
- Runtimes: Versioning and support.