CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-inversify

A powerful and lightweight inversion of control container for JavaScript and Node.js apps powered by TypeScript.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

binding.mddocs/

Binding Configuration

InversifyJS provides a fluent API for configuring service bindings, allowing you to specify how services are created, their lifecycle scope, and conditional resolution rules. The binding system supports various patterns from simple class binding to complex factory configurations.

Fluent Binding Syntax

BindToFluentSyntax

Defines what the service identifier should be bound to.

interface BindToFluentSyntax<T> {
  to(constructor: Newable<T>): BindInWhenOnFluentSyntax<T>;
  toSelf(): BindInWhenOnFluentSyntax<T>;
  toConstantValue(value: T): BindWhenOnFluentSyntax<T>;
  toDynamicValue(func: (context: ResolutionContext) => T): BindInWhenOnFluentSyntax<T>;
  toFactory<T2>(factory: Factory<T2>): BindWhenOnFluentSyntax<T>;
  toProvider<T2>(provider: Provider<T2>): BindWhenOnFluentSyntax<T>;
}

BindInFluentSyntax

Defines the scope (lifetime) of the binding.

interface BindInFluentSyntax<T> {
  inSingletonScope(): BindWhenOnFluentSyntax<T>;
  inTransientScope(): BindWhenOnFluentSyntax<T>;
  inRequestScope(): BindWhenOnFluentSyntax<T>;
}

Combined Syntax Interfaces

interface BindInWhenOnFluentSyntax<T> extends BindInFluentSyntax<T>, BindWhenFluentSyntax<T>, BindOnFluentSyntax<T> {}
interface BindWhenOnFluentSyntax<T> extends BindWhenFluentSyntax<T>, BindOnFluentSyntax<T> {}

Binding Targets

Bind to Class Constructor

Bind a service identifier to a specific class constructor.

interface IWarrior {
  fight(): string;
}

@injectable()
class Ninja implements IWarrior {
  fight() { return "Ninja fighting!"; }
}

// Bind interface to implementation
container.bind<IWarrior>("Warrior").to(Ninja);

Bind to Self

Bind a class to itself, useful when no interface abstraction is needed.

@injectable()
class DatabaseService {
  connect() { /* ... */ }
}

// Bind class to itself
container.bind(DatabaseService).toSelf();

// Alternative syntax
container.bind<DatabaseService>("DatabaseService").to(DatabaseService);

Bind to Constant Value

Bind to a pre-existing value or configuration object.

// Simple constant
container.bind<string>("DatabaseUrl").toConstantValue("mongodb://localhost:27017");

// Configuration object
const config = {
  apiKey: "secret-key",
  endpoint: "https://api.example.com",
  timeout: 5000
};
container.bind<typeof config>("Config").toConstantValue(config);

// Array of values
container.bind<string[]>("AllowedOrigins").toConstantValue([
  "http://localhost:3000",
  "https://example.com"
]);

Bind to Dynamic Value

Bind to a function that computes the value at resolution time.

// Current timestamp
container.bind<Date>("CurrentTime").toDynamicValue(() => new Date());

// Environment-based configuration
container.bind<string>("DatabaseUrl").toDynamicValue(() => {
  return process.env.NODE_ENV === "production" 
    ? "mongodb://prod-server:27017"
    : "mongodb://localhost:27017";
});

// Context-aware values
container.bind<Logger>("Logger").toDynamicValue((context) => {
  const parentName = context.currentRequest.parentRequest?.serviceIdentifier;
  return new Logger(`[${parentName}]`);
});

Bind to Factory

Bind to a factory function for complex object creation.

// Factory type definition
type Factory<T> = () => T;

// Database connection factory
container.bind<Factory<IConnection>>("ConnectionFactory").toFactory(() => {
  return () => new DatabaseConnection(process.env.DATABASE_URL);
});

// Usage
const connectionFactory = container.get<Factory<IConnection>>("ConnectionFactory");
const connection = connectionFactory();

// Factory with dependencies
container.bind<Factory<IEmailService>>("EmailFactory").toFactory((context) => {
  return () => {
    const config = context.container.get<IConfig>("Config");
    const logger = context.container.get<ILogger>("Logger");
    return new EmailService(config, logger);
  };
});

Bind to Provider

Bind to an async provider function for asynchronous initialization.

// Provider type definition
type Provider<T> = () => Promise<T>;

// Async database connection
container.bind<Provider<IDatabase>>("DatabaseProvider").toProvider(() => {
  return async () => {
    const db = new Database();
    await db.connect();
    await db.migrate();
    return db;
  };
});

// Usage
const dbProvider = container.get<Provider<IDatabase>>("DatabaseProvider");
const database = await dbProvider();

// Provider with dependencies
container.bind<Provider<ICache>>("CacheProvider").toProvider((context) => {
  return async () => {
    const config = context.container.get<IConfig>("Config");
    const cache = new RedisCache(config.redis);
    await cache.connect();
    return cache;
  };
});

Binding Scopes

Singleton Scope

Creates a single instance shared across the entire container.

@injectable()
class DatabaseService {
  private connection?: IConnection;
  
  async connect() {
    if (!this.connection) {
      this.connection = await createConnection();
    }
    return this.connection;
  }
}

// Single instance for entire application
container.bind<DatabaseService>("Database").to(DatabaseService).inSingletonScope();

// Both resolve to same instance
const db1 = container.get<DatabaseService>("Database");
const db2 = container.get<DatabaseService>("Database");
console.log(db1 === db2); // true

Transient Scope

Creates a new instance every time the service is requested.

@injectable()
class RequestHandler {
  private id = Math.random();
  
  getId() { return this.id; }
}

// New instance every time
container.bind<RequestHandler>("Handler").to(RequestHandler).inTransientScope();

const handler1 = container.get<RequestHandler>("Handler");
const handler2 = container.get<RequestHandler>("Handler");
console.log(handler1.getId() === handler2.getId()); // false

Request Scope

Creates one instance per resolution request (dependency graph resolution).

@injectable() 
class RequestContext {
  private requestId = Math.random();
  
  getRequestId() { return this.requestId; }
}

@injectable()
class ServiceA {
  constructor(@inject("RequestContext") private context: RequestContext) {}
  
  getContextId() { return this.context.getRequestId(); }
}

@injectable()
class ServiceB {
  constructor(@inject("RequestContext") private context: RequestContext) {}
  
  getContextId() { return this.context.getRequestId(); }
}

container.bind<RequestContext>("RequestContext").to(RequestContext).inRequestScope();
container.bind<ServiceA>("ServiceA").to(ServiceA);
container.bind<ServiceB>("ServiceB").to(ServiceB);

// Same request context shared within single resolution
const serviceA = container.get<ServiceA>("ServiceA");
const serviceB = container.get<ServiceB>("ServiceB");
console.log(serviceA.getContextId() === serviceB.getContextId()); // true

// Different request context for new resolution
const serviceA2 = container.get<ServiceA>("ServiceA");
console.log(serviceA.getContextId() === serviceA2.getContextId()); // false

Advanced Binding Patterns

Multiple Bindings

Bind multiple implementations to the same service identifier.

interface IPlugin {
  execute(): void;
}

@injectable() class PluginA implements IPlugin { execute() { /* ... */ } }
@injectable() class PluginB implements IPlugin { execute() { /* ... */ } }
@injectable() class PluginC implements IPlugin { execute() { /* ... */ } }

// Multiple bindings
container.bind<IPlugin>("Plugin").to(PluginA);
container.bind<IPlugin>("Plugin").to(PluginB);
container.bind<IPlugin>("Plugin").to(PluginC);

// Get all implementations
const plugins = container.getAll<IPlugin>("Plugin");
plugins.forEach(plugin => plugin.execute());

Binding Identifier Types

interface BindingIdentifier<T = any> {
  serviceIdentifier: ServiceIdentifier<T>;
  name?: string;
  tags?: { [key: string]: any };
}

type BindingActivation<T> = (context: ResolutionContext, injectable: T) => T | Promise<T>;
type BindingDeactivation<T> = (injectable: T) => void | Promise<void>;

interface BindingConstraints {
  (request: Request): boolean;
}

Contextual Binding Information

Access binding context during dynamic value creation:

container.bind<string>("ContextInfo").toDynamicValue((context) => {
  const request = context.currentRequest;
  return `Service: ${request.serviceIdentifier.toString()}, Target: ${request.target?.name}`;
});

Binding Syntax Chaining

The fluent syntax allows chaining multiple configuration methods:

container
  .bind<IEmailService>("EmailService")
  .to(EmailService)
  .inSingletonScope()
  .whenTargetNamed("primary")
  .onActivation((context, service) => {
    console.log("Email service activated");
    return service;
  });

Best Practices

  1. Use appropriate scopes: Singleton for shared resources, Transient for stateless services
  2. Prefer constructor injection over factory: Simpler and more testable
  3. Use constants for configuration: Avoid hardcoding values in classes
  4. Leverage dynamic values: For environment-specific or computed values
  5. Chain binding methods: Create readable binding configurations
  6. Consider request scope: For request-specific shared state
  7. Use factories sparingly: Only when complex initialization is required

docs

binding.md

conditional.md

container.md

decorators.md

index.md

lifecycle.md

modules.md

tile.json