Tsyringe for TypeScript: Complete Guide with Examples
In modern TypeScript development, managing dependencies is a critical task, especially when building scalable and testable applications. The tsyringe library is a lightweight, powerful dependency injection (DI) container built entirely on TypeScript decorators. It lets developers declaratively describe dependencies between components, automatically resolve them, and manage object lifecycles.
Why do you need tsyringe? In large projects, manually creating class instances and passing them to constructors leads to tight coupling, complicates testing, and makes it hard to swap implementations. Tsyringe solves these problems by providing a centralized registry (container) that knows how to create and provide dependencies. You simply specify which classes or tokens should be registered, and the container automatically injects them where needed. This makes your code cleaner, more modular, and significantly simplifies writing unit tests.
The library heavily uses TypeScript decorators (@injectable(), @inject()), making the syntax intuitive for developers familiar with Angular or NestJS. Tsyringe requires no complex configuration, works out of the box with modern TypeScript versions, and supports various registration strategies (singleton, transient, scoped). In this article, we'll dive deep into every aspect of working with tsyringe, from installation to advanced use cases.
Installation
To install tsyringe in your TypeScript project, run the following command using npm:
npm install tsyringe reflect-metadata
After installation, make sure your project has decorator support enabled. In your tsconfig.json file, include the following options:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Also, in your application's entry point (e.g., index.ts), import reflect-metadata once to enable metadata for decorators:
import "reflect-metadata";
// ... rest of your code
Quick Start: Minimal Working Example
Let's create a simple application that demonstrates the basic mechanics of tsyringe. We'll have a database service that gets injected into a controller.
import "reflect-metadata";
import { container, injectable, inject } from "tsyringe";
// 1. Define the service to be injected
@injectable()
class DatabaseService {
getData(): string {
return "Data from the database";
}
}
// 2. Define the controller that depends on DatabaseService
@injectable()
class UserController {
constructor(
@inject(DatabaseService) private dbService: DatabaseService
) {}
getUserData(): string {
return this.dbService.getData();
}
}
// 3. Register classes in the container (optional here,
// since tsyringe can auto-resolve registered classes)
container.registerSingleton(DatabaseService);
container.registerSingleton(UserController);
// 4. Use the container to get an instance of the controller
const controller = container.resolve(UserController);
console.log(controller.getUserData()); // Output: "Data from the database"
This example shows three key steps: mark the class as @injectable(), specify dependencies via @inject() in the constructor, and retrieve an instance using container.resolve(). Note that if a class is registered with the @injectable() decorator, tsyringe can often resolve it automatically without explicit registration.
Also in library
Dependency Injection Container in C++: Fruit Library for Dependency Injection
Integration of Prometheus Metrics in Java Applications: simpleclient Library
CLI Framework for Rust: Complete Guide to Developing Utilities