A lightweight dependency injection library for JavaScript applications.
npm install universal-common-dependencyinjection
This library provides a simple, flexible dependency injection system for JavaScript applications:
-
ServiceCollection
- Container for registering services with different lifetimes -
ServiceProvider
- Resolves registered services when needed in your application
import { ServiceCollection, ServiceProvider } from "universal-common-dependencyinjection";
class Logger {
log(message) {
console.log(`[LOG]: ${message}`);
}
}
class UserService {
#logger;
constructor(logger) {
this.#logger = logger;
}
getUser(id) {
this.#logger.log(`Getting user with ID: ${id}`);
return { id, name: "John Doe" };
}
}
// Create a service collection
const services = new ServiceCollection();
// Register services
services.addSingleton(Logger, () => new Logger());
services.addTransient(UserService, (serviceProvider) => {
const logger = serviceProvider.getRequiredService(Logger);
return new UserService(logger);
});
// Build the service provider
const serviceProvider = services.buildServiceProvider();
// Resolve and use services
const userService = serviceProvider.getRequiredService(UserService);
const user = userService.getUser(123);
console.log(user); // { id: 123, name: "John Doe" }
Singleton services are created once and the same instance is returned for each request:
class DatabaseConnection {
constructor() {
console.log("Opening database connection...");
}
query(sql) {
console.log(`Executing query: ${sql}`);
}
}
const services = new ServiceCollection();
services.addSingleton(DatabaseConnection, () => new DatabaseConnection());
const serviceProvider = services.buildServiceProvider();
const db1 = serviceProvider.getService(DatabaseConnection);
const db2 = serviceProvider.getService(DatabaseConnection);
console.log(db1 === db2); // true
Transient services are created each time they are requested:
class RequestHandler {
#requestId;
constructor() {
this.#requestId = Math.random().toString(36).substr(2, 9);
console.log(`Created request handler: ${this.#requestId}`);
}
get requestId() {
return this.#requestId;
}
}
const services = new ServiceCollection();
services.addTransient(RequestHandler, () => new RequestHandler());
const serviceProvider = services.buildServiceProvider();
const handler1 = serviceProvider.getService(RequestHandler);
const handler2 = serviceProvider.getService(RequestHandler);
console.log(handler1 === handler2);
console.log(handler1.requestId);
console.log(handler2.requestId);
You can register pre-built instances as singletons:
class Configuration {
#settings;
constructor(settings) {
this.#settings = settings;
}
getSetting(key) {
return this.#settings[key];
}
}
const config = new Configuration({
apiUrl: "https://api.example.com",
timeout: 5000
});
const services = new ServiceCollection();
// Register the pre-built instance
services.addSingleton(Configuration, config);
const serviceProvider = services.buildServiceProvider();
// The pre-built instance is returned
const resolvedConfig = serviceProvider.getService(Configuration);
console.log(resolvedConfig.getSetting("apiUrl")); // https://api.example.com
You can handle optional dependencies using getService()
:
class AnalyticsService {
#logger;
constructor(logger) {
this.#logger = logger || {
log: () => {}
};
}
trackEvent(name, data) {
this.#logger.log(`Tracking event: ${name}`);
// Implementation
}
}
const services = new ServiceCollection();
services.addTransient(AnalyticsService, (provider) => {
const logger = provider.getService(Logger);
return new AnalyticsService(logger);
});
const serviceProvider = services.buildServiceProvider();
const analytics = serviceProvider.getRequiredService(AnalyticsService);
analytics.trackEvent("page_view", { path: "/home" });
Services can depend on other services, which will be resolved automatically:
class AuthService {
authenticate(credentials) {
return { userId: 1, role: "admin" };
}
}
class UserRepository {
getById(id) {
return { id, name: "John Doe" };
}
}
class UserController {
#authService;
#userRepository;
constructor(authService, userRepository) {
this.#authService = authService;
this.#userRepository = userRepository;
}
login(credentials) {
const authResult = this.#authService.authenticate(credentials);
if (authResult) {
const user = this.#userRepository.getById(authResult.userId);
return {
user,
token: "jwt-token-here"
};
}
return null;
}
}
const services = new ServiceCollection();
services.addSingleton(AuthService, () => new AuthService());
services.addSingleton(UserRepository, () => new UserRepository());
// UserController depends on AuthService and UserRepository
services.addTransient(UserController, (provider) => {
const authService = provider.getRequiredService(AuthService);
const userRepository = provider.getRequiredService(UserRepository);
return new UserController(authService, userRepository);
});
const serviceProvider = services.buildServiceProvider();
const userController = serviceProvider.getRequiredService(UserController);
const loginResult = userController.login({ username: "john", password: "secret" });
console.log(loginResult);