bun add fast-injectionimport { Container } from "fast-injection";
import { singleton } from "fast-injection/decorators";
@singleton()
class Logger {
log(msg: string) {
console.log(msg);
}
}
const container = new Container();
container.register(Logger); // Decorator controls lifetime
const logger = container.resolve(Logger);
logger.log("Hello, fast-injection!");import { Container } from "fast-injection";
import { singleton } from "fast-injection/decorators";
@singleton()
class Database {
connect() {
return "Connected";
}
}
class UserService {
constructor(private db: Database) {}
getUsers() {
this.db.connect();
return ["Alice", "Bob"];
}
}
const container = new Container();
// Decorator controls lifetime
container.register(Database);
// Factory for dependency injection
container.registerFactory(UserService, (c) => {
const db = c.resolve(Database);
return new UserService(db);
});
const service = container.resolve(UserService);
console.log(service.getUsers());import { Container } from "fast-injection";
import { singleton, scoped, inject } from "fast-injection/decorators";
@singleton()
class Database {
query() {
/* ... */
}
}
@scoped()
class RequestHandler {
constructor(@inject(Database) private db: Database) {}
}
const container = new Container();
container.register(Database);
// For each HTTP request
const requestScope = container.createScope();
// Register request-specific data
requestScope.registerValue("RequestId", Math.random());
// Resolve services in request scope
const handler = requestScope.resolve(RequestHandler);
// Clean up after request
await requestScope.dispose();Use getGlobalContainer() to access a shared container instance across your entire application without passing it around:
import { getGlobalContainer, resetGlobalContainer, Lifetime } from "fast-injection";
// Setup at application startup (e.g., in main.ts)
function setupContainer() {
const container = getGlobalContainer();
container.register(ConfigService, { lifetime: Lifetime.Singleton });
container.register(Logger, { lifetime: Lifetime.Singleton });
container.registerFactory(ApiClient, (c) => new ApiClient(c.resolve(ConfigService), c.resolve(Logger)), {
lifetime: Lifetime.Transient,
});
}
// Access from anywhere in your application
function someModule() {
const container = getGlobalContainer();
const config = container.resolve(ConfigService);
console.log(config.get("apiUrl"));
}
async function anotherModule() {
const container = getGlobalContainer();
const apiClient = container.resolve(ApiClient);
await apiClient.fetchData("/users");
}
// Cleanup when shutting down
async function shutdown() {
await resetGlobalContainer(); // Disposes and resets the container
}container.registerAsyncFactory(Database, async () => {
const db = new Database();
await db.connect();
return db;
});
const db = await container.resolveAsync(Database);class DatabaseService {
private connection?: Connection;
async onInit() {
console.log("Connecting to database...");
this.connection = await connect();
}
async onDispose() {
console.log("Closing database connection...");
await this.connection?.close();
}
}
container.register(DatabaseService, { lifetime: Lifetime.Singleton });
const db = container.resolve(DatabaseService);
// Later, clean up
await container.dispose(); // Calls onDispose on all servicesimport { createTestContainer } from "fast-injection/testing";
// In your tests
const container = createTestContainer();
// Mock dependencies
const mockDb = {
query: () => Promise.resolve([{ id: 1, name: "Test" }]),
};
container.registerValue(Database, mockDb);
// Test your service with mocked dependencies
const service = container.resolve(UserService);While decorators are the recommended default, explicit options are useful when:
import { Lifetime } from "fast-injection";
// Override decorator at registration
@singleton()
class MyService {}
container.register(MyService, { lifetime: Lifetime.Transient }); // Override
// Dynamic registration without decorators
container.register(ThirdPartyClass, { lifetime: Lifetime.Singleton });
// Factory with explicit lifetime
container.registerFactory(ComplexService, (c) => new ComplexService(c.resolve(Dep)), { lifetime: Lifetime.Singleton });You can use decorators to control service lifetimes and document your code. Decorators are fully functional - they automatically control lifetimes when you use register() without explicit options.
import { Container } from "fast-injection";
import { singleton, transient, scoped } from "fast-injection/decorators";
@singleton()
class ConfigService {
private config = { apiUrl: "https://api.example.com" };
getConfig() {
return this.config;
}
}
@transient()
class Logger {
private requestId = Math.random().toString(36);
log(msg: string) {
console.log(`[${this.requestId}] ${msg}`);
}
}
@scoped()
class RequestContext {
constructor(public readonly requestId: string) {}
}
const container = new Container();
// Decorators control lifetimes automatically!
container.register(ConfigService);
container.register(Logger);
container.register(RequestContext);
// ConfigService will be singleton
const config1 = container.resolve(ConfigService);
const config2 = container.resolve(ConfigService);
console.log(config1 === config2); // true
// Logger will be transient (new instance each time)
const logger1 = container.resolve(Logger);
const logger2 = container.resolve(Logger);
console.log(logger1 === logger2); // falseimport { injectable, inject } from "fast-injection/decorators";
const ILogger = Symbol("ILogger");
const IDatabase = Symbol("IDatabase");
interface ILogger {
log(msg: string): void;
}
interface IDatabase {
query(sql: string): Promise<any>;
}
@injectable()
class ConsoleLogger implements ILogger {
log(msg: string) {
console.log(msg);
}
}
@injectable()
class PostgresDatabase implements IDatabase {
async query(sql: string) {
return { rows: [] };
}
}
// @inject decorator helps document which tokens are used
@injectable()
class UserService {
constructor(
@inject(ILogger) private logger: ILogger,
@inject(IDatabase) private db: IDatabase,
) {}
async getUsers() {
this.logger.log("Fetching users from database");
const result = await this.db.query("SELECT * FROM users");
return result.rows;
}
}
const container = new Container();
// Register implementations
container.register(ILogger, ConsoleLogger);
container.register(IDatabase, PostgresDatabase);
// No factory needed: @inject annotated constructor parameters will be resolved
container.register(UserService);
const service = container.resolve(UserService);
await service.getUsers();import { singleton } from "fast-injection/decorators";
@singleton()
class Database {
connect() {
return "Connected";
}
}
@singleton()
class Logger {
log(msg: string) {
console.log(msg);
}
}
// No decorator - needs manual injection
class UserService {
constructor(
private db: Database,
private logger: Logger,
) {}
getUsers() {
this.logger.log("Fetching users");
return ["Alice", "Bob"];
}
}
const container = new Container();
// Decorators control lifetimes!
container.register(Database);
container.register(Logger);
// No decorator on UserService — provide a factory to wire dependencies,
// or annotate constructor parameters with @inject(...) and register the class directly
container.registerFactory(UserService, (c) => {
return new UserService(c.resolve(Database), c.resolve(Logger));
});
const service = container.resolve(UserService);| Use Case | Recommended Approach |
|---|---|
| Simple services | Use @singleton(), @transient(), @scoped() |
| Marking injectables | Use @injectable() (transient by default) |
| Services with dependencies | Use factories - registerFactory() |
| Interface tokens | Use factories with symbol tokens |
| Override decorator lifetime | Use registration options - { lifetime } |
Important: Decorators automatically control lifetimes when using container.register(). For classes that are not decorated or do not have @inject(...) annotations on constructor parameters, you still need to use registerFactory() to provide dependency wiring.
register<T>(token, target?, options?)- Register a serviceregisterValue<T>(token, value)- Register a valueregisterFactory<T>(token, factory, options?)- Register a factoryregisterAsyncFactory<T>(token, factory, options?)- Register async factoryregisterAll<T>(token, implementations, options?)- Register multiple implementationsresolve<T>(token)- Resolve a serviceresolveAsync<T>(token)- Resolve a service asynchronouslyresolveAll<T>(token)- Resolve all implementationshas(token)- Check if service is registeredcreateScope()- Create child scoped containerdispose()- Dispose all services
Lifetime.Singleton- One instance per containerLifetime.Transient- New instance every timeLifetime.Scoped- One instance per scope
{
lifetime: Lifetime.Singleton | Lifetime.Transient | Lifetime.Scoped,
replace: boolean // Allow overwriting existing registrations
}@injectable()- Mark class as injectable (transient lifetime)@singleton()- Mark class as singleton lifetime@transient()- Mark class as transient lifetime@scoped()- Mark class as scoped lifetime@inject(token)- Explicitly inject dependency token
Decorators control lifetimes automatically when using container.register(). Explicit lifetime options override decorator metadata:
// Decorator sets lifetime
@singleton()
class MyService {}
container.register(MyService); // Uses Singleton from decorator
// Explicit option overrides decorator
container.register(MyService, { lifetime: Lifetime.Transient }); // Overrides to TransientWhen using decorators, enable experimentalDecorators in your tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}# Run all examples
bun run examples/run-all.ts
# Individual examples
bun run examples/01-simple-registration.ts # Basic registration
bun run examples/02-dependency-injection.ts # Factories and DI
bun run examples/03-scoped-containers.ts # Scoped lifetimes
bun run examples/04-async-factories.ts # Async registration
bun run examples/05-lifecycle-hooks.ts # onInit/onDispose
bun run examples/06-testing-mocks.ts # Testing utilities
bun run examples/07-decorator-lifetimes.ts # Decorator documentation
bun run examples/08-inject-decorator.ts # Interface tokens
bun run examples/09-decorators-factories.ts # Combined approach
# Original examples (still available)
bun run examples/basic.ts # Comprehensive demo
bun run examples/http-server.ts # HTTP server example
bun run examples/factory.ts # Factory patterns# Run all tests
bun test
# Run tests in watch mode
bun test --watch
# Run with coverage
bun test --coverageUse registerFactory() when your service requires manual wiring (third-party classes, dynamic construction, or when you are not using decorators).
If your class is decorated and uses @inject(...) or @injectable(), you can register the class directly with container.register() and the container will inject dependencies automatically.
// Factory for manual wiring
container.registerFactory(UserService, (c) => {
return new UserService(c.resolve(Database), c.resolve(Logger));
});
// Or, with decorators and @inject, no factory is needed:
import { singleton, inject } from "fast-injection/decorators";
@singleton()
class UserService {
constructor(
@inject(Database) private db: Database,
@inject(Logger) private logger: Logger,
) {}
}
container.register(UserService); // Dependencies injected automatically// Database connections, configuration, loggers
container.register(Database, { lifetime: Lifetime.Singleton });
container.register(Logger, { lifetime: Lifetime.Singleton });// Request context, user session
container.register(RequestContext, { lifetime: Lifetime.Scoped });// Always dispose containers when done
try {
const service = container.resolve(MyService);
// ... use service
} finally {
await container.dispose();
}// Type-safe interface tokens
interface IUserRepository {
findById(id: string): Promise<User>;
}
const IUserRepository = Symbol("IUserRepository");
container.register(IUserRepository, UserRepository);
const repo = container.resolve<IUserRepository>(IUserRepository);Make sure you register services before resolving:
container.register(MyService);
const service = container.resolve(MyService); // ✅Use factory registration for services with dependencies:
container.registerFactory(ServiceWithDeps, (c) => {
return new ServiceWithDeps(c.resolve(Dependency));
});Refactor your services to avoid circular dependencies, or use lazy resolution:
container.registerFactory(ServiceA, (c) => {
return new ServiceA(() => c.resolve(ServiceB)); // Lazy
});- Look at examples in
examples/directory - Review tests in
tests/for usage patterns - Check out the full documentation
fast-injection - Built with ❤️ by 21no.de | MIT License