DI Container

Nova includes a built-in dependency injection container powered by Inversify. Register services in different scopes and inject them into controllers seamlessly.

The Di Class

The Di class wraps Inversify's Container and provides a clean API for registering services:

di.singleton(impl, abstraction?)

Registers a class as a singleton. The same instance is shared across all injections.

ParameterTypeRequiredDescription
impl Constructor<T> Required The implementation class.
abstraction AbstractConstructor<T> Optional Abstract class to bind to (for interface-based DI).

di.transient(impl, abstraction?)

Creates a new instance every time the service is injected.

di.request(impl, abstraction?)

Creates one instance per request scope.

di.instance(token, value)

Binds a constant value to a token. Useful for configuration objects.

di.factory(token, factory)

Binds a factory function (resolved lazily as a singleton).

Registration Methods

1. Via app.register()

main.ts TypeScript
const app = await NovaFactory.create();
const di = app.register();

di.singleton(UserService);
di.transient(EmailService);
di.instance('CONFIG', { apiKey: 'abc123' });
di.factory('DB_CONNECTION', () => createConnection());

2. Via Module

user.module.ts TypeScript
export const UserModule = new Module()
  .registerController(UserController)
  .registerSingleton(UserService)
  .registerSingleton(PostgresUserRepo, IUserRepository);

Interface-Based Injection

Use abstract classes as interfaces for clean DI:

user.repository.ts TypeScript
// Abstract interface
abstract class IUserRepository {
  abstract findAll(): Promise<User[]>;
  abstract findById(id: number): Promise<User | null>;
}

// Concrete implementation
class PostgresUserRepository extends IUserRepository {
  async findAll() { return []; }
  async findById(id: number) { return null; }
}

// Register
di.singleton(PostgresUserRepository, IUserRepository);

Injecting Services

Use the @inject decorator from Inversify (re-exported by Nova):

user.controller.ts TypeScript
import { Controller, Get, inject } from '@abrahambass/nova';

@Controller('/users')
class UserController {
  constructor(
    @inject(IUserRepository) private repo: IUserRepository,
  ) {}

  @Get()
  async findAll() {
    return this.repo.findAll();
  }
}

Optional Dependencies

Use @optional for dependencies that may not be registered:

Example TypeScript
import { inject, optional } from '@abrahambass/nova';

constructor(
  @inject(CacheService) @optional() private cache?: CacheService,
) {}