A powerful and lightweight inversion of control container for JavaScript and Node.js apps powered by TypeScript.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
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>;
}Defines the scope (lifetime) of the binding.
interface BindInFluentSyntax<T> {
inSingletonScope(): BindWhenOnFluentSyntax<T>;
inTransientScope(): BindWhenOnFluentSyntax<T>;
inRequestScope(): BindWhenOnFluentSyntax<T>;
}interface BindInWhenOnFluentSyntax<T> extends BindInFluentSyntax<T>, BindWhenFluentSyntax<T>, BindOnFluentSyntax<T> {}
interface BindWhenOnFluentSyntax<T> extends BindWhenFluentSyntax<T>, BindOnFluentSyntax<T> {}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 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 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 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 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 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;
};
});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); // trueCreates 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()); // falseCreates 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()); // falseBind 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());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;
}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}`;
});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;
});