This page documents the 0.4 release candidates of @nexusdi/core. The newest on npm is 0.4.0-rc.1. The documentation for 0.3.2, the current release, is at nexus.js.org. Try the release candidate.
NexusDI vs Other Containers
When choosing a dependency injection container, the primary trade-offs are typically bundle size, startup performance, and the ability to detect wiring mistakes.
NexusDI is compared here against four other TypeScript containers using a standardized benchmark harness. Every figure on this page is derived from actual measurements of the same eight-provider graph. The page renders each figure from the harness’s results files at build time. Understanding Benchmark Measurements states the method behind each one.
Performance and Footprint
The performance table below compares bundle size, cold-start time, and resolution speed.
- Bundle Size: The minified and gzipped esbuild bundle size.
- Startup Time: Cold start of a fresh Node process.
- Resolve Speed: Time for a single transient resolution on a ready container.
- Build Time: The library’s fastest passing build of a 200-class graph under a toolchain its documentation names.
Each library appears in the variant its own documentation teaches. Focus or hover a figure for its spread and, on another library’s row, the paired ratio to NexusDI with its interval. A ratio below 1 means NexusDI took less time, and an interval that contains 1 is no measured difference.
Performance comparison
| Library | Bundle size (min+gzip) | Startup | Resolve | Build time |
|---|---|---|---|---|
| NexusDI plain | 19.0 kBminified 57.5 kB | 53.1 msMAD 2.01 msp5 49.5 msp95 58.4 msready 152 µs | 124 nsMAD 2.5 nsp5 120 nsp95 142 ns | 264 ms (bun)MAD 2.98 msp5 259 msp95 269 ms |
| InversifyJS decorated | 22.0 kBminified 91.7 kBdecorator metadata polyfill 4.7 kB of it | 81.8 msMAD 1.94 msp5 77.6 msp95 87.7 msready 165 µsNexusDI ÷ this: 0.65× (95% interval 0.65× to 0.65×) | 66.3 nsMAD 1.12 nsp5 64.1 nsp95 75.2 nsNexusDI ÷ this: 1.87× (95% interval 1.87× to 1.88×) | 518 ms (tsgo)MAD 4.35 msp5 512 msp95 530 msNexusDI ÷ this: 0.51× (95% interval 0.50× to 0.51×) |
| tsyringe decorated | 9.8 kBminified 30.6 kBdecorator metadata polyfill 4.7 kB of it | 34 msMAD 1.31 msp5 31.8 msp95 38.3 msready 2.54 µsNexusDI ÷ this: 1.55× (95% interval 1.54× to 1.56×) | 193 nsnoisyMAD 15.5 nsp5 167 nsp95 224 nsNexusDI ÷ this: 0.68× (95% interval 0.67× to 0.68×) | 486 ms (tsgo)MAD 7.11 msp5 477 msp95 505 msNexusDI ÷ this: 0.54× (95% interval 0.53× to 0.54×) |
| awilix plain | 3.9 kBminified 10.1 kB | 43.6 msMAD 1.17 msp5 41.3 msp95 47.4 msready 61.5 µsNexusDI ÷ this: 1.21× (95% interval 1.21× to 1.22×) | 131 nsnoisyMAD 7.06 nsp5 122 nsp95 153 nsNexusDI ÷ this: 0.96× (95% interval 0.96× to 0.97×) | 261 ms (bun)MAD 2.39 msp5 256 msp95 266 msNexusDI ÷ this: 1.02× (95% interval 1.00× to 1.02×) |
| needle-di decorated | 3.4 kBminified 10.5 kB | 27.7 msMAD 1 msp5 25.6 msp95 31.2 msready 12.9 µsNexusDI ÷ this: 1.92× (95% interval 1.91× to 1.93×) | not measured: needle-di documents singletons only. | 263 ms (bun)MAD 2.17 msp5 260 msp95 270 msNexusDI ÷ this: 1.00× (95% interval 0.99× to 1.01×) |
Toolchain Flexibility
Many DI libraries rely on emitDecoratorMetadata, which requires specific compiler flags that not every toolchain supports. In the matrix below, the metadata-based variants fail to compile under Node’s type stripping, and tsyringe’s fails at runtime under esbuild. Every variant passes under Bun and Deno.
NexusDI avoids this by using static deps. Because static deps needs no compiler-emitted metadata, it passes under every toolchain in the matrix. The @nexusdi/decorators variant uses standard decorators, which plain Vite and Node’s type stripping cannot compile.
A cell that does not pass keeps its first error line in a tooltip and links the toolchain’s own page when that page documents the limitation. InversifyJS’s getting-started page and tsyringe’s README set experimentalDecorators and emitDecoratorMetadata for their decorated setups. awilix and needle-di document setups that use no decorator metadata (inversify 8.2.3, tsyringe 4.10.0, awilix 13.0.5, needle-di 1.2.1; read 2026-09-30).
Toolchain matrix
| Library and variant | tsc | tsgo | esbuild | swc | babel | vite | vite8+babel-plugin | bun | deno | node-strip-types |
|---|---|---|---|---|---|---|---|---|---|---|
| awilix plain | pass | pass | pass | pass | pass | pass | pass | pass | pass | pass |
| InversifyJS decorated | pass | pass | pass | pass | pass | pass | pass | pass | passWarning experimentalDecorators compiler option is deprecated and may be removed at any time | compile errorSyntaxError: Invalid or unexpected token documented |
| InversifyJS decorated-explicit | pass | pass | pass | pass | compile errorSyntaxError: Invalid or unexpected token | pass | compile errorerror during build: Build failed with 7 errors: [PARSE_ERROR] Decorators are not valid here. | pass | passWarning experimentalDecorators compiler option is deprecated and may be removed at any time | compile errorSyntaxError: Invalid or unexpected token documented |
| InversifyJS plain | pass | pass | pass | pass | pass | pass | pass | pass | pass | pass |
| needle-di decorated | pass | pass | pass | pass | pass | compile errorSyntaxError: Invalid or unexpected token documented | pass | pass | pass | compile errorSyntaxError: Invalid or unexpected token documented |
| needle-di plain | pass | pass | pass | pass | pass | pass | pass | pass | pass | pass |
| NexusDI decorated | pass | pass | pass | pass | pass | compile errorSyntaxError: Invalid or unexpected token documented | pass | pass | pass | compile errorSyntaxError: Invalid or unexpected token documented |
| NexusDI plain | pass | pass | pass | pass | pass | pass | pass | pass | pass | pass |
| tsyringe decorated | pass | pass | runtime errorError: TypeInfo not known for "QuantumComputer" documented | pass | pass | pass | pass | pass | passWarning experimentalDecorators compiler option is deprecated and may be removed at any time | compile errorSyntaxError: Invalid or unexpected token documented |
| tsyringe decorated-explicit | pass | pass | pass | pass | compile errorSyntaxError: Invalid or unexpected token | pass | compile errorerror during build: Build failed with 8 errors: [PARSE_ERROR] Decorators are not valid here. | pass | passWarning experimentalDecorators compiler option is deprecated and may be removed at any time | compile errorSyntaxError: Invalid or unexpected token documented |
| tsyringe plain | pass | pass | pass | pass | pass | pass | pass | pass | pass | pass |
Wiring-Mistake Detection
Most containers are “optimistic”. They assume the graph is correct and only fail at runtime when a missing dependency is requested. This can lead to crashes in production on rarely-used code paths.
NexusDI is “pessimistic.” It validates the entire module graph during the Nexus.create phase. If a dependency is missing, Nexus.create reports it before any constructor runs. One error lists every problem it found (Errors).
The probe table adds one mistake at a time to the graph and shows where each library reports it.
Wiring-mistake probes
| Mistake | NexusDI | InversifyJS | tsyringe | awilix | needle-di |
|---|---|---|---|---|---|
| A provider is never registered | container creationMissingProviderError: [NEXUS_MISSING_PROVIDER] token=NavCharts requester=Bridge module=Meridian. https://nexus.js.org/errors/NEXUS_MISSING_PROVIDER | first resolveError: No bindings found for service: "Symbol(NavCharts)". | first resolveError: Cannot inject the dependency "charts" at position #1 of "Bridge" constructor. Reason: Attempted to resolve unregistered dependency token: "NavCharts" | first resolveAwilixResolutionError: Could not resolve 'charts'. | first resolveError: No provider(s) found for InjectionToken "NavCharts" |
| Two providers depend on each other | container creationCircularDependencyError: [NEXUS_CIRCULAR_DEPENDENCY] path=PowerRouter,ShieldGrid,PowerRouter. https://nexus.js.org/errors/NEXUS_CIRCULAR_DEPENDENCY | first resolveError: Circular dependency found: Bridge -> ShieldGrid -> PowerRouter -> ShieldGrid | never | first resolveAwilixResolutionError: Could not resolve 'shield'. Cyclic dependencies detected. | first resolveError: Detected circular dependency: Bridge -> ShieldGrid -> PowerRouter -> ShieldGrid. Please change your dependency graph or use lazy injection instead. |
| A singleton depends on a scoped provider | container creationLifetimeError: [NEXUS_LIFETIME_VIOLATION] path=Bridge,FlightLog lifetimes=singleton,scoped. https://nexus.js.org/errors/NEXUS_LIFETIME_VIOLATION | not applicable | never | first resolveAwilixResolutionError: Could not resolve 'flightLog'. Dependency 'flightLog' has a shorter lifetime than its ancestor: 'bridge' | not applicable |
| A dependency has the wrong type | type checksrc/main.ts - error TS2769: No overload matches this call. | never | never | type checksrc/main.ts - error TS2322: Type 'PowerRouter' is not assignable to type 'FusionReactor'. | never |
| A missing provider and a cycle together | container creation, names 2 of 2MissingProviderError: [NEXUS_MISSING_PROVIDER] token=NavCharts requester=Bridge module=Meridian. https://nexus.js.org/errors/NEXUS_MISSING_PROVIDER | first resolve, names 1 of 2Error: No bindings found for service: "Symbol(NavCharts)". | first resolve, names 1 of 2Error: Cannot inject the dependency "charts" at position #1 of "Bridge" constructor. Reason: Attempted to resolve unregistered dependency token: "NavCharts" | first resolve, names 1 of 2AwilixResolutionError: Could not resolve 'charts'. | first resolve, names 1 of 2Error: No provider(s) found for InjectionToken "NavCharts" |
Lifecycle and Disposal Comparison
Managing resources (like database pools) is critical. NexusDI implements a strict reverse-creation order for disposal and uses SuppressedError to ensure every resource is closed, even if some failures occur. Lifecycle and Disposal explains why each row matters.
A cell says what the library does without extra plugins unless it names one.
| Feature | NexusDI | InversifyJS | tsyringe | awilix | needle-di | TypeDI | NestJS |
|---|---|---|---|---|---|---|---|
Dependency-ordered onInit | Yes | Per instance, on first resolve | No | No | No | No | Yes |
| Reverse-order Disposal | Yes | No | No | No | n/a | No | Yes |
| Reports all disposal failures | Yes | No | No | No | n/a | No | Logged |
| Scopes own transients | Yes | No | Yes | No | n/a | No | No |
| Disposes factory results | Yes | Partial | No | With .disposer() | n/a | Yes | Yes |
The harness does not measure this table: it is read from each library’s source and documentation, and the notes are in specs/2026-10-03-lifecycle-comparison-research.md.
The detailed version of the same comparison, row by row:
| NexusDI | InversifyJS | tsyringe | awilix | needle-di | TypeDI | NestJS | |
|---|---|---|---|---|---|---|---|
| Startup hook awaited in dependency order | Yes, onInit before create resolves | Per instance on first resolve, awaited through getAsync | No hook | No hook | No hook | No hook | Yes, onModuleInit |
| Container disposal | Symbol.asyncDispose | unbindAllAsync() | dispose() | dispose() | None | reset() calls destroy() | app.close() |
Supports Symbol.dispose and Symbol.asyncDispose | Both | Both, with @inversifyjs/plugin-dispose | No | No | No | No | No |
| Disposal order | Reverse creation | Unspecified, plugin runs fewest dependents first | Creation order, so dependencies first | All at once | n/a | Registration order | Reverse of startup |
| All disposer failures reported | Yes, SuppressedError chain | No, first error | No, first error | No, first error | n/a | No, errors ignored | Logged, not thrown |
| Keeps going after a failure | Yes | Plugin stops at the first | Async failures only, a sync throw skips the rest | Yes | n/a | Yes | Yes |
| Scopes own transients | Yes | No | Yes, by the container that resolved it | No | n/a | No | No, request-scoped providers get no hooks |
| Disposes factory results | Yes | Only with onDeactivation | No | Yes, with .disposer() | n/a | Yes | Yes |
Leaves useValue objects alone | Yes | Yes, unless onDeactivation is set | Yes | Yes | n/a | Yes | No, hooks run on them |
NestJS has the most complete startup story of the six. It awaits hooks module by module and by dependency depth inside a module, runs shutdown in the reverse order and logs every failure. Its hooks are provider methods, and a Nest application is not an AsyncDisposable. tsyringe disposes every disposable instance it builds, transients included. awilix awaits any promise a .disposer() returns, and a container disposes only its own cache, never its scopes. InversifyJS has @postConstruct and @preDestroy, and with its dispose plugin it supports await using. needle-di and TypeDI document no startup hook and no awaited disposal.
Versions read on 2026-10-03: inversify 8.2.3 (with @inversifyjs/plugin-dispose 0.4.7), tsyringe 4.10.0, awilix 13.0.5, needle-di 1.2.1, TypeDI 0.10.0, @nestjs/core 12.1.2. Sources: the published packages’ code, InversifyJS container API , tsyringe disposable instances , awilix disposing , needle-di docs and NestJS lifecycle events .
Containers Left Out
TypeDI is left out of the measurements: its last release, 0.10.0, was published on 2021-01-15 (npm ). NestJS is a framework with its own container, so it is left out too.
Conclusion
If you are building a large-scale application where reliability, startup speed, and toolchain flexibility are priorities, NexusDI’s static validation and lightweight core make it a strong choice. For those already using the NestJS framework, its built-in container already wires your services, and NexusDI would add a second container beside it. See When to skip NexusDI.
Break and Fix a Benchmark Figure
Every figure on this page comes from the harness, and a guard keeps it that way. The <Figure> component renders one result inline in a sentence, and the doc-benchmark-figures guard fails on a path the results do not hold.
Try it: In apps/docs/content/index.mdx, change the figure’s path from size.nexusdi.plain.esbuild.gzip to size.nexusdi.plain.esbuild.brotli, and run the guard:
npx nx test @nexusdi/repo-checks -- doc-benchmark-figuresThe guard fails and names the key the results lack. Type a size with its unit into a sentence on the same page, and the guard fails on that sentence too, because a figure in prose comes only from the harness. Restore the path and delete the sentence, and the guard passes.
How This Comparison Was Measured
The NexusDI maintainer wrote the harness and measured these results, and has an interest in them. Each library’s fixture follows its own documentation, and Understanding Benchmark Measurements lists what was pinned, what was tested, what was left out, and how to report a fixture that departs from a library’s documentation.
Measured with
- Libraries
- NexusDI 0.4.0-rc.0InversifyJS 8.2.3 with a decorator metadata polyfilltsyringe 4.10.0 with a decorator metadata polyfillawilix 13.0.5needle-di 1.2.1
- Toolchains
- tsc 6.0.3tsgo 7.0.2esbuild 0.28.2swc 1.16.2babel 8.0.6vite 8.3.0vite8+babel-plugin 8.3.0bun 1.4.2deno 2.9.6node-strip-types 24.20.0bundlers: esbuild 0.28.2, rollup 4.63.4
- Runtime
- Node 24.20.0
- Runner
- INTEL(R) XEON(R) PLATINUM 8573C, 4 cores, 16 GB, Linux, GitHub-hosted
- Timings run
- 2026-10-01, seed 36891702544, core commit 8824502
- Results files
matrix.jsonat a9231afprobes.jsonat a9231afsize.jsonat f699eb1build.jsonat 20d5e27timings/2026-10-01-8824502.jsonat 20d5e27