Getting Started

Decorator-free Dependency Injection for TypeScript

TypeScript 5+ Zero Decorators Browser-First Type-Safe 3.9KB gzipped

⚡ The AI Way

Let AI do the heavy lifting. Copy our prompt, paste it to Claude/ChatGPT, and you're done.

AI Setup (60 seconds)

🛠️ The Manual Way

Prefer hands-on? Follow step-by-step instructions to set up NovaDI yourself.

Manual Setup

⚡ AI-Assisted Setup (Recommended)

NovaDI is designed to be AI-friendly. Instead of memorizing APIs and patterns, just give your AI assistant this prompt and let it handle the setup.

💡 Why AI-First?
Dependency injection frameworks traditionally have steep learning curves. NovaDI embraces modern development: give AI the context it needs, and it writes clean DI code that follows best practices automatically.

Step 1: Copy the Prompt

Copy this entire prompt and paste it into Claude, ChatGPT, or your favorite AI coding assistant:

I want to use the @novadi/core dependency injection library in my TypeScript project.

Key Principles:
- Package: @novadi/core
- NO decorators/annotations in business code
- Convention over configuration
- Uses .as<T>() and .resolveType<T>()
- TypeScript transformer handles type names automatically

Core API:
1. Import: import { Container } from '@novadi/core'

2. Build container:
   const container = new Container()
   const builder = container.builder()

3. Register services:
   builder.registerType(ConsoleLogger).as<ILogger>().singleInstance()

4. Autowire dependencies BY CONVENTION (recommended):
   builder.registerType(UserService).as<UserService>().autoWire()
   // Parameters automatically match registered interfaces by naming convention

5. Build and resolve:
   const app = builder.build()
   const service = app.resolveType<UserService>()

Lifetimes:
- .singleInstance() - singleton
- .instancePerDependency() - transient (DEFAULT)
- .instancePerRequest() - per resolution tree

AutoWire (Convention Over Configuration):
- Automatic: .autoWire() - matches parameters to interfaces by naming convention
- Explicit: .autoWire({ map: { logger: (c) => c.resolveType<ILogger>() } })
- Use automatic for ALL services, explicit only for primitives/values

Transformer Setup (tsconfig.json):
{
  "compilerOptions": {
    "plugins": [
      { "transform": "@novadi/core/transformer" }
    ]
  }
}

Then: npm install -D ts-patch && npx ts-patch install

Simple Hello World example:
```typescript
import { Container } from '@novadi/core'

interface IGreeter {
  greet(name: string): string
}

class ConsoleGreeter implements IGreeter {
  greet(name: string): string {
    return `Hello, ${name}!`
  }
}

class Application {
  constructor(private greeter: IGreeter) {}

  run() {
    console.log(this.greeter.greet('World'))
  }
}

// Composition Root
const container = new Container()
const builder = container.builder()

builder.registerType(ConsoleGreeter).as<IGreeter>().singleInstance()
builder.registerType(Application).as<Application>().autoWire() // Convention!

const app = builder.build()
const application = app.resolveType<Application>()
application.run() // Outputs: Hello, World!
```

Please help me set up NovaDI following these patterns.

Step 2: Let AI Set It Up

Your AI assistant will:

  • Install @novadi/core
  • Configure the TypeScript transformer
  • Create a basic container setup
  • Write clean, decorator-free code following NovaDI conventions

Step 3: Start Coding

That's it! Your project is now set up with dependency injection. Start writing your services:

// Your business logic - clean and framework-free
interface IUserRepository {
  findById(id: string): Promise
}

class UserRepository implements IUserRepository {
  async findById(id: string): Promise {
    // Database logic
  }
}

class UserService {
  constructor(private userRepo: IUserRepository) {}

  async getUser(id: string) {
    return this.userRepo.findById(id)
  }
}

// Composition Root (AI can help set this up)
builder.registerType(UserRepository).as().singleInstance()
builder.registerType(UserService).as().autoWire() // ✨ Convention!
🤖 Pro Tip: When you need to add new services, just ask your AI: "Add a new EmailService with IEmailService interface using NovaDI conventions" - it knows the patterns!

🛠️ Manual Setup

Prefer to set things up yourself? Here's the step-by-step Documentation.

1. Installation

npm install @novadi/core
# or
yarn add @novadi/core
# or
pnpm add @novadi/core

2. Transformer Setup

The TypeScript transformer automatically injects type names at compile-time. Choose one method:

Option A: Modern Bundlers (Recommended ⭐)

Use unplugin for universal bundler support:

Vite:
// vite.config.ts
import { defineConfig } from 'vite'
import { NovadiUnplugin } from '@novadi/core/unplugin'

export default defineConfig({
  plugins: [NovadiUnplugin.vite()]
})
webpack:
// webpack.config.js
const { NovadiUnplugin } = require('@novadi/core/unplugin')

module.exports = {
  plugins: [NovadiUnplugin.webpack()]
}
Rollup:
// rollup.config.js
import { NovadiUnplugin } from '@novadi/core/unplugin'

export default {
  plugins: [NovadiUnplugin.rollup()]
}
esbuild:
// esbuild.config.js
const { NovadiUnplugin } = require('@novadi/core/unplugin')

require('esbuild').build({
  plugins: [NovadiUnplugin.esbuild()]
})

Option B: TypeScript Compiler (tsc)

For direct tsc compilation, use ts-patch:

npm install -D ts-patch
npx ts-patch install

Add to tsconfig.json:

{
  "compilerOptions": {
    "plugins": [
      { "transform": "@novadi/core/transformer" }
    ]
  }
}
⚠️ Option C: Manual Type Names (Not Recommended)
You can provide type names manually, but this defeats the purpose of NovaDI's clean API. Only use this for runtime-only environments (tsx, ts-node) with no build step. See docs.

3. Basic Usage

Create your first container:

import { Container } from '@novadi/core'

// 1. Define your services (clean code, no decorators!)
interface ILogger {
  log(message: string): void
}

class ConsoleLogger implements ILogger {
  log(message: string) {
    console.log(`[LOG] ${message}`)
  }
}

class UserService {
  constructor(private logger: ILogger) {}

  createUser(name: string) {
    this.logger.log(`Creating user: ${name}`)
  }
}

// 2. Configure container (Composition Root)
const container = new Container()
const builder = container.builder()

// Register implementations
builder.registerType(ConsoleLogger).as().singleInstance()

// AutoWire does ALL the wiring by convention!
builder.registerType(UserService).as().autoWire()

const app = builder.build()

// 3. Resolve and use
const userService = app.resolveType()
userService.createUser('Alice') // [LOG] Creating user: Alice
✅ That's it! No manual configuration. No mapping. Just .autoWire() - convention over configuration.

🚀 Next Steps

📚 Complete Documentation

Learn about lifetimes, patterns, and advanced features

Read the Documentation →

🏗️ Design Philosophy

Understand why NovaDI is built the way it is

Design Philosophy →

💻 GitHub

View source code, examples, and contribute

View on GitHub →