Zero DependenciesZero ReflectionRuntime Agnostic

Dependency injection without the reflection tax.

A lightweight, zero-reflection, runtime-agnostic Dependency Injection framework and AOT compiler for modern JavaScript and TypeScript. Runs identically on Node.js, Deno, Bun, and the edge.

TypeScript· quickstart
// src/user.service.ts
import {Injectable} from "@nonnajs/di";
import {UserRepository, User} from "./user.repository";
@Injectable()
export class UserService {
// With @nonnajs/compiler, UserRepository is inferred as the DI token.
constructor(private readonly userRepo: UserRepository) {}
getUser(id: string): User | undefined {
return this.userRepo.findById(id);
}
}
// src/index.ts
import "./__generated__/nonna-dependencies.generated";
import {Nonna} from "@nonnajs/di";
const injector = await Nonna.injector().scan().build();
const userService = injector.get(UserService);
console.log(userService.getUser("u1")); // { id: 'u1', name: 'Alice' }
await injector.destroy();

One core. Every runtime. Every framework.

  • Node.js
  • Deno
  • Bun
  • React
  • Vue
  • Svelte
  • Web Components
  • Stencil

// why nonna

Six architectural decisions, no adjectives.

Every pillar below is a concrete engineering constraint the runtime is built around — not a marketing bullet.

Design philosophy: "shift as much DI reasoning as possible into compile/initialization time, leaving an extremely small and predictable runtime."

01

Zero Runtime Dependencies

@nonnajs/di ships an empty dependencies object and never requires reflect-metadata. Checked by a guardrail test, not just claimed.

02

True Runtime Agnostic

Runs unmodified on Node.js, Deno, Bun, and edge/Workers runtimes using standard node:async_hooks AsyncLocalStorage and Web Standards.

03

Ahead-of-Time Compilation

@nonnajs/compiler statically inspects your TypeScript with the real TypeChecker at build time — constructor tokens are known before the process even starts.

04

First-Class Request Scoping

Built-in request scope isolates state per async execution flow (injector.runInScope()), with strict guardrails preventing request-scoped leaks into singletons.

05

Deterministic Lifecycle

Predictable OnInit/OnDestroy hooks with reverse-order teardown and aggregated error reporting.

06

Pluggable & Extensible

First-class multi-providers (multi: true), sync/async factories, alias tokens, and full container inspection APIs.

// see it in your stack

Looks native, because it is.

Same injector, same tokens — resolved through the idiom your framework already uses.

TypeScript· index.ts
// src/index.ts
import "./__generated__/nonna-dependencies.generated";
import {Nonna} from "@nonnajs/di";
import {UserService} from "./user.service";
async function bootstrap() {
const injector = await Nonna.injector().scan().build();
const userService = injector.get(UserService);
console.log(userService.getUser("u1")); // { id: 'u1', name: 'Alice' }
await injector.destroy();
}
bootstrap();
See full example

// performance

Performance work that goes past “it's fast”.

Integer-keyed registrations instead of Symbol() allocation. Memoized dependency metadata instead of a WeakMap hit per resolution. A mutable stack + Set for circular-dependency tracking on the hot synchronous path. AsyncLocalStorage skipped entirely when nothing is request-scoped. Independent eager providers booting concurrently instead of one at a time.

/**

* Origin story. Nonna was born on vacation in Rome, coded during the "cold hours" in a hotel room while the kids watched TV — a tentative replacement for TypeDI in an existing project, driven by a deceptively simple question: how "pure" (zero-reflection) and fast can a DI framework actually get if you stop assuming reflect-metadata has to be part of the deal?

*/

Stop paying the reflection tax.