I built Injectus, a decorator-free DI container for Node, looking for early users/feedback

typescript dev.to

I've been building a dependency injection container for Node/TypeScript called Injectus, and I'm at the point where I'd rather have a handful of people actually try it and poke holes in it than keep polishing alone. It's 0.2.x not "adopt this in prod tomorrow" young, but the core is solid and I want real usage before I push it further.

What it is: zero-dependency, decorator-free IoC container. No reflect-metadata, no emitDecoratorMetadata, no build config wrestling.

import { Injector, inject, InjectionToken } from "injectus";

const DB_URL = new InjectionToken<string>("DB_URL");

class Database {
  url = inject(DB_URL);
}

class UserService {
  db = inject(Database);
  findAll() {
    return this.db.url;
  }
}

const injector = Injector.create({
  providers: [
    { provide: DB_URL, useValue: "postgres://localhost/app" },
    Database,
    UserService,
  ],
});

injector.resolve(UserService).findAll();
await injector.dispose();
Enter fullscreen mode Exit fullscreen mode

Why I built it: I kept hitting the same friction with existing containers - decorators requiring specific tsconfig/build setups, string or symbol tokens silently colliding, and singleton/scoped lifetime mismatches only surfacing as bugs in production. Injectus's actual differentiators:

  • Decorator-free - inject() works in field initializers and factory bodies, no metadata reflection required.
  • Identity-based tokens - classes and InjectionToken instances are the key, not their string description. Two tokens named "Logger" can't collide.
  • Captive dependency detection - resolving a Singleton that depends on a Scoped binding throws CaptiveDependencyError at resolve time, not silently in prod.
  • Deterministic disposal - LIFO teardown via Symbol.asyncDispose, multiple failing disposers collected into one AggregateError, full dependency path exposed root-to-leaf on CircularDependencyError/CaptiveDependencyError.

It also benchmarks well against the two closest points of comparison - awilix (decorator-free) and tsyringe (decorator-based) - resolving the same dependency-graph shape in each case:

What's honestly not done yet, so you know what you're getting into:

  • No module system yet - (flat provider arrays only, for now)
  • No multi-injection - registering the same token twice currently shadows (last-write-wins)
  • No async init hooks / lifecycle ordering yet
  • No framework adapters (Express example exists, others don't)

Source: dev.to

arrow_back Back to Tutorials