NovaDI
Decorator-free Dependency Injection for TypeScript
Inspired by Autofac, built for modern TypeScript projects. Fast, type-safe, and framework-agnostic.
True Separation of Concerns
Zero decorators in your domain code. No @Injectable() pollution.
All DI configuration in ONE place at the composition root - just like Autofac in .NET.
Industry-Leading Speed
Fastest singleton resolution (0.03ms) and fastest complex graph (0.03ms) among all tested frameworks. No reflection overhead, just pure TypeScript performance.
Modern & Universal
Only 3.93KB gzipped. Works everywhere: Browser, Node.js, Deno, Edge Workers. Built for TypeScript 5+ with full type safety and zero runtime dependencies. Tested with 5 major bundlers (Vite, Webpack, Rollup, esbuild, Parcel) using unplugin for seamless integration.
Quick Start (30 seconds)
Get up and running with NovaDI in less than a minute
1. Install
npm install @novadi/core
2. Use
import { Container } from '@novadi/core'
const container = new Container()
.builder()
.registerType(ConsoleLogger)
.as<ILogger>()
.singleInstance()
.build()
const logger = container.resolveType<ILogger>()
logger.log('Hello NovaDI!')
Why NovaDI?
Most DI frameworks require decorators everywhere in your domain code. NovaDI takes a different approach.
❌ Other Frameworks
// Decorators pollute your domain code
@Injectable()
class UserService {
constructor(
@Inject('Logger') private logger: ILogger,
@Inject('Database') private db: IDatabase
) {}
}
- Tight coupling to framework
- Violates separation of concerns
- Hard to test in isolation
- Reflection overhead
✅ NovaDI Approach
// Clean domain code
class UserService {
constructor(
private logger: ILogger,
private db: IDatabase
) {}
}
// DI config in ONE place (composition root)
const container = new Container()
.builder()
.registerType(Logger).as<ILogger>()
.registerType(Database).as<IDatabase>()
.registerType(UserService).autoWire()
.build()
- Framework-agnostic domain code
- True separation of concerns
- Easy to test
- Zero runtime reflection
💡 The Composition Root Pattern
NovaDI follows the Composition Root pattern: all DI configuration happens in ONE place at your application's entry point. Your domain code stays clean and framework-agnostic.
Key Features
Everything you need for modern dependency injection
Zero Decorators
Keep your domain code clean and framework-agnostic. No @Injectable(), no @Inject().
Industry-Leading Performance
Fastest singleton resolution (0.03ms) and complex graphs (0.03ms). Wins on speed with decorator-free design.
Type-Safe
Full TypeScript support without reflect-metadata. Leverages the type system for safety.
Autofac-Inspired Builder
Fluent, readable API inspired by the excellent Autofac (.NET) container.
Autowiring
Automatic dependency resolution with paramName, map, or class-based strategies.
Lightweight
3.93KB gzipped. Zero runtime dependencies. Tree-shakeable for optimal bundle size.
Universal
Works everywhere: Node.js, Browser, Edge Workers, Deno. No Node-specific APIs.
Keyed Services
Register multiple implementations of the same interface with keys.
Child Containers
Scoped isolation for per-request patterns and hierarchical DI.
Performance
Benchmarked against popular DI frameworks
| Framework | Decorator-Free | Singleton (ms) ↓ | Transient (ms) ↓ | Build Time (ms) ↓ | Complex Graph (ms) ↓ | Bundle Size (KB) ↓ |
|---|---|---|---|---|---|---|
| NovaDI 🏆 | ✅ Yes | 0.03 🥇 | 0.09 | 0.11 | 0.03 🥇 | 3.9 |
| Brandi | ✅ Yes | 0.11 | 0.22 | 0.12 | 0.07 | 2.2 🥇 |
| InversifyJS | ❌ No | 0.14 | 0.31 | 0.29 | 0.12 | 16.8 |
| TSyringe | ❌ No | 0.19 | 0.28 | 0.02 🥇 | 8.45 | 7.4 |
| TypeDI | ❌ No | 0.03 🥇 | 0.03 🥇 | 3.41 | 0.21 | 6.4 |
📊 Benchmark Details
Benchmarks run on AMD Ryzen 7 PRO 6850H with 32GB RAM, measuring average resolution time over 100 runs.
Simple transients: classes with no dependencies.
Complex graph: deep dependency tree with 5+ levels.
💡 About "Complex Graph"
The "Complex Graph" benchmark isn't actually complex - it's just how real enterprise applications are structured. This isn't a contrived "hello world" demo meant to impress with artificial depth.
A typical application service (like UserService) needs a repository, which needs an HTTP client, which needs configuration and logging. That's 3-4 dependency levels - exactly what you'd build in any production app. The benchmark measures how fast the container resolves this realistic object graph, not some artificial "look how deep we can go" scenario.
Real-World Example
Complete application setup with multiple services
1. Define Interfaces (Domain Layer)
// interfaces.ts - Clean, no DI knowledge
interface ILogger {
log(message: string): void
error(message: string): void
}
interface IDatabase {
query(sql: string): Promise<any>
}
interface IUserService {
getUser(id: string): Promise<User>
createUser(data: CreateUserDto): Promise<User>
}
2. Implement Services
// implementations.ts
class ConsoleLogger implements ILogger {
log(message: string) {
console.log(`[LOG] ${message}`)
}
error(message: string) {
console.error(`[ERROR] ${message}`)
}
}
class PostgresDatabase implements IDatabase {
async query(sql: string) {
// ... database logic
}
}
class UserService implements IUserService {
constructor(
private logger: ILogger,
private db: IDatabase
) {}
async getUser(id: string) {
this.logger.log(`Fetching user ${id}`)
return this.db.query(`SELECT * FROM users WHERE id = $1`, [id])
}
async createUser(data: CreateUserDto) {
this.logger.log(`Creating user ${data.email}`)
return this.db.query(`INSERT INTO users ...`, data)
}
}
3. Configure DI (Infrastructure Layer)
// composition-root.ts - ALL DI config in ONE place
const container = new Container()
.builder()
// Register infrastructure services
.registerType(ConsoleLogger)
.as<ILogger>()
.singleInstance()
.registerType(PostgresDatabase)
.as<IDatabase>()
.singleInstance()
// Register domain services with autowiring
.registerType(UserService)
.as<IUserService>()
.autoWire({
map: {
logger: (c) => c.resolveType<ILogger>(),
db: (c) => c.resolveType<IDatabase>()
}
})
.singleInstance()
.build()
4. Use in Application
// app.ts
const userService = container.resolveType<IUserService>()
// All dependencies automatically injected!
const user = await userService.getUser('123')
console.log(user)
Ready to Get Started?
Build better TypeScript applications with true dependency injection
Documentation
📘 Getting Started
Learn the basics: installation, first container, core concepts, and common patterns.
Read Documentation →📗 Complete Documentation
All features from easy to advanced: autowiring, keyed services, child containers, performance.
Explore features →📙 DI Theory & Philosophy
Why dependency injection matters and why decorators are an anti-pattern.
Learn theory →