TSyringe Dependency Injection Container
TSyringe is a lightweight dependency injection (DI) container for JavaScript and TypeScript, primarily focusing on constructor injection. Currently at stable version 4.10.0, it is actively maintained and backed by Microsoft. Its release cadence appears to be feature-driven, with recent updates (v4.4.0) adding interception and transformation capabilities. Key differentiators include its strong integration with TypeScript decorators (`@injectable`, `@singleton`, `@inject`), its ability to handle complex dependency graphs including circular dependencies with a `delay` helper, and its provision for both class-based and interface-based injection using tokens. It requires `reflect-metadata` for decorator-based type reflection and specific `tsconfig.json` settings.
Common errors
-
No matching binding found for token: [object Object]
cause A class or interface token was requested from the container but was never registered, or the `@injectable()` decorator was omitted from a class intended for injection.fixEnsure that all classes intended for injection are decorated with `@injectable()` and that any interfaces or custom tokens have a corresponding `container.register()` call. -
TypeError: Reflect.metadata is not a function
cause The `reflect-metadata` polyfill was either not imported, imported incorrectly, or imported after TSyringe or decorated classes were loaded.fixVerify `reflect-metadata` is installed (`npm install reflect-metadata`) and that `import "reflect-metadata";` is the absolute first line in your application's entry point. -
SyntaxError: Decorators are not enabled.
cause The TypeScript compiler options `experimentalDecorators` or `emitDecoratorMetadata` are not correctly set in `tsconfig.json`, or Babel is not configured for metadata emission.fixCheck your `tsconfig.json` for `"experimentalDecorators": true` and `"emitDecoratorMetadata": true`. If using Babel, ensure `babel-plugin-transform-typescript-metadata` is installed and configured in your Babel plugins.
Warnings
- gotcha TSyringe heavily relies on TypeScript's decorator metadata. You must enable `experimentalDecorators` and `emitDecoratorMetadata` in your `tsconfig.json` under `compilerOptions`.
- gotcha A polyfill for the Reflect API (e.g., `reflect-metadata`) is a mandatory runtime dependency. It must be imported exactly once in your application's entry point, *before* any TSyringe code or decorated classes are loaded.
- gotcha If you are using Babel (e.g., in a React Native project), you must install and configure `babel-plugin-transform-typescript-metadata` to ensure TypeScript metadata is correctly emitted.
- gotcha Handling circular dependencies requires using the `delay` helper function when registering or injecting. Without it, you will encounter runtime errors or `undefined` dependencies.
Install
-
npm install tsyringe -
yarn add tsyringe -
pnpm add tsyringe
Imports
- injectable
const { injectable } = require('tsyringe');import { injectable } from 'tsyringe'; - container
import Container from 'tsyringe';
import { container } from 'tsyringe'; - singleton
import { Singleton } from 'tsyringe';import { singleton } from 'tsyringe'; - inject
import { Inject } from 'tsyringe';import { inject } from 'tsyringe';
Quickstart
import "reflect-metadata"; // Must be imported ONCE at the very top of your application entry point
import { container, injectable, inject, singleton } from "tsyringe";
// 1. Define an interface for abstraction
interface IDatabaseService {
connect(): void;
getData(query: string): string;
}
// 2. Implement the interface and mark as injectable
@injectable()
class RealDatabaseService implements IDatabaseService {
connect() {
console.log("Connected to a real database.");
}
getData(query: string): string {
return `Data for: ${query} from RealDatabase`;
}
}
// 3. Define a service that depends on IDatabaseService
@injectable()
class DataProcessorService {
constructor(@inject("IDatabaseService") private dbService: IDatabaseService) {
this.dbService.connect();
}
processUserData(userId: string): string {
const query = `SELECT * FROM users WHERE id = ${userId}`;
return `Processing user data: ${this.dbService.getData(query)}`;
}
}
// 4. Register the interface with its implementation in the container
container.register<IDatabaseService>("IDatabaseService", {
useClass: RealDatabaseService,
});
// 5. Resolve the DataProcessorService
const dataProcessor = container.resolve(DataProcessorService);
console.log(dataProcessor.processUserData("123"));
// 6. Example of a singleton service
@singleton()
class AppConfig {
public readonly API_KEY = process.env.API_KEY ?? 'default-api-key';
constructor() {
console.log("AppConfig initialized (should only happen once for singleton).");
}
}
const config1 = container.resolve(AppConfig);
const config2 = container.resolve(AppConfig);
console.log(`API Key: ${config1.API_KEY}`);
console.log(`Are config instances identical? ${config1 === config2}`);
/*
NOTE: Ensure your tsconfig.json includes:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
*/