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.
// src/user.service.tsimport {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.tsimport "./__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."
Zero Runtime Dependencies
@nonnajs/di ships an empty dependencies object and never requires reflect-metadata. Checked by a guardrail test, not just claimed.
True Runtime Agnostic
Runs unmodified on Node.js, Deno, Bun, and edge/Workers runtimes using standard node:async_hooks AsyncLocalStorage and Web Standards.
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.
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.
Deterministic Lifecycle
Predictable OnInit/OnDestroy hooks with reverse-order teardown and aggregated error reporting.
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.
// src/index.tsimport "./__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();
// performance
Performance work that goes past “it's fast”.
Integer-keyed registrations instead ofSymbol()allocation. Memoized dependency metadata instead of aWeakMaphit per resolution. A mutable stack +Setfor circular-dependency tracking on the hot synchronous path.AsyncLocalStorageskipped 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?
*/