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.
Getting Started
Most dependency injection containers are “optimistic”. They assume your wiring is correct and only tell you something is missing when your app crashes at runtime.
NexusDI takes a different approach. It validates your entire dependency graph during startup. If a dependency is missing or a circular reference exists, NexusDI catches it immediately, ensuring that once your app starts, it stays running.
This tutorial will guide you through installing NexusDI, building a simple application, and intentionally breaking it to see how the validation system helps you fix errors.
Installation
NexusDI 0.4 is currently a release candidate. To get started, create a new project and install the core package using the next tag.
mkdir meridian && cd meridian
npm init -y
npm pkg set type=module
npm install @nexusdi/core@next
npm install -D typescript tsx @types/node@nexusdi/core is published as ES modules. If you are using Node 22.12 or later, you can use require() in a CommonJS project, but ESM is recommended.
TypeScript Configuration
NexusDI requires TypeScript 5.4 or later. To avoid conflicts with legacy DI patterns, ensure that experimentalDecorators and emitDecoratorMetadata are disabled in your settings. The core API uses no decorators, and the optional @nexusdi/decorators package uses standard decorators. (A project that needs the flag for another library can keep it, as Using NexusDI with experimentalDecorators shows.) The package’s type declarations reference TypeScript’s esnext.disposable library, which adds Symbol.asyncDispose and AsyncDisposable to your program, so await using compiles with lib: ["es2022"] and you add no entry for it.
Create a tsconfig.json with the following configuration:
Not run
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"strict": true,
"noEmit": true,
"lib": ["es2022"],
"types": ["node"]
}
}You can now type-check your project with npx tsc and run files directly using npx tsx <file>.ts.
Your First Application
The simplest way to start a container is using Nexus.create. This function takes a list of classes, analyzes how they depend on each other, and instantiates them in the correct order.
In NexusDI, a class declares its dependencies using a static deps property. TypeScript ensures this list matches the constructor parameters. get() then returns the instance, and await using disposes the container when the block ends.
Save the following as ship.ts:
import { Nexus } from '@nexusdi/core';
class FusionReactor {
readonly output = 1.21;
}
class QuantumComputer {
static deps = [FusionReactor] as const;
constructor(readonly reactor: FusionReactor) {}
status(): string {
return `ShipComputer online. Reactor output ${this.reactor.output} GW.`;
}
}
await using ship = await Nexus.create([FusionReactor, QuantumComputer]);
const status = ship.get(QuantumComputer).status(); // -> 'ShipComputer online. Reactor output 1.21 GW.'
console.log(status);Run it with:
npx tsx ship.tsYou should see: ShipComputer online. Reactor output 1.21 GW.
Try it: Change output to 2.5 and run it again. The status line follows the reactor, because NexusDI passed the FusionReactor it built into the QuantumComputer constructor.
Experience Static Validation
To see the power of NexusDI, let’s intentionally break the wiring.
If a class depends on a token that the container wasn’t given, Nexus.create will reject the startup process with a NEXUS_BLUEPRINT_INVALID error. This prevents your app from starting in a broken state.
Try it: Delete FusionReactor from the array passed to Nexus.create in ship.ts and run it again.
You receive a clear error listing NEXUS_MISSING_PROVIDER. The run stops with an uncaught NEXUS_BLUEPRINT_INVALID error. Nexus.create checks the whole graph before it builds anything, and its errors array holds one error for each problem. This program breaks the ship the same way and reads the errors array of the rejection:
import { Nexus, isNexusError } from '@nexusdi/core';
class FusionReactor {
readonly output = 1.21;
}
class QuantumComputer {
static deps = [FusionReactor] as const;
constructor(readonly reactor: FusionReactor) {}
}
const error = await Nexus.create([QuantumComputer]).catch(
(caught: unknown) => caught,
);
if (!isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')) throw error;
const code = error.code; // -> 'NEXUS_BLUEPRINT_INVALID'
console.log(code);
const first = error.errors[0]?.message; // -> '[NEXUS_MISSING_PROVIDER] token=FusionReactor requester=QuantumComputer module=root. https://nexus.js.org/errors/NEXUS_MISSING_PROVIDER'
console.log(first);The one error inside is NEXUS_MISSING_PROVIDER. Core writes a short message: the code with the error’s fields as key=value pairs, then a link to the code’s page. The module=root field names the module the array became: Nexus.create compiles a list of classes as an unnamed root module.
Fix the ship: put FusionReactor back in the array and run ship.ts again. The status line prints.
Improving Error Messages with DevTools
While core provides concise error codes, the @nexusdi/devtools plugin transforms these into human-readable messages with suggested fixes. It also adds the graph and trace tools that Introspection and Tracing teaches. Use the same version as core.
Install the devtools plugin:
npm install -D @nexusdi/devtools@nextRegister the plugin in your application: the program below registers it only when NODE_ENV is not production, and it leaves FusionReactor out on purpose, so it is the broken ship from the previous section:
import { Nexus, isNexusError } from '@nexusdi/core';
import { devtools } from '@nexusdi/devtools';
class FusionReactor {
readonly output = 1.21;
}
class QuantumComputer {
static deps = [FusionReactor] as const;
constructor(readonly reactor: FusionReactor) {}
}
const dev = process.env.NODE_ENV !== 'production';
const error = await Nexus.create([QuantumComputer], {
plugins: dev ? [devtools()] : [],
}).catch((caught: unknown) => caught);
if (!isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')) throw error;
const message = error.errors[0]?.message.split('\n'); // -> ['[NEXUS_MISSING_PROVIDER] QuantumComputer (module root) depends on FusionReactor, but no provider of FusionReactor is visible in root.', ' Fix: provide FusionReactor in root or in a module root imports.']
console.log(message);Now, when you run your program with an error, you’ll see a full explanation and a direct path to the fix. You can keep these tools in development and omit them in production to keep your bundle size minimal.
Try it: Save the program as errors.ts and run npx tsx errors.ts. The terminal prints the full message and its fix line. Run it again as NODE_ENV=production npx tsx errors.ts: the plugin list is empty, and the same error prints core’s short message. Every later page of these docs builds on this project.