A fast, feature-rich dependency injection container for JavaScript and TypeScript.
Works with ECMAScript decorators, legacy TypeScript decorators, or plain configuration — no decorators required.
Contributing Guidelines
·
Submit an Issue
DiCaf is built for speed and developer experience — zero dependencies and written in vanilla TypeScript. The only exception is reflect-metadata, which is opt-in and only needed if you choose legacy TypeScript decorators over the standard ECMAScript ones.
- Three usage modes — ECMAScript decorators, legacy
experimentalDecorators, or fully programmatic. - Binding targets — classes, abstract classes, interfaces, functions, and arbitrary values.
- Profiles — segregate components by environment or configuration profile.
- Injection options — single, array, object, map, and optional injection descriptors.
- Conditionals — conditionally register components based on runtime conditions.
- Scopes — singleton, transient, request, container, and refreshable scopes.
- Mixed scopes — mix different scopes in the same dependency graph without extra configuration.
- Async factories — resolve dependencies that require asynchronous initialization.
- Scan — automatically scan and register decorated types; no manual module configuration needed.
- Testing utilities — first-class support for overrides and isolated containers in tests.
- Extensible — custom scopes, injection resolvers, metadata readers, post-processors, and hooks.
- ESM + CJS — dual-format package.
- Runtime agnostic — works in Node.js, browser, Deno, and Bun.
npm install @caffeine-projects/dicafFor legacy TypeScript decorators, also install:
npm install reflect-metadata # only when using TypeScript legacy decoratorsThree rules to keep in mind:
- Initialize before resolving. Call
await container.init()before anyget()call. - Bind before initializing. The container is sealed after
init()— registering new bindings afterwards is not allowed. - Decorated files must be loaded first. Decorators are evaluated when the module is imported. If a file hasn't been imported at least once before
init(), its components are invisible to the container. The Scan feature makes this task straightforward.
import { DiCaf } from '@caffeine-projects/dicaf'
import { Injectable } from '@caffeine-projects/dicaf/decorators'
@Injectable()
class GreetingService {
greet(name: string) {
return `Hello, ${name}!`
}
}
const container = new DiCaf()
// All bindings must be registered before this line
await container.init()
// Safe to resolve now
const svc = container.get(GreetingService)
console.log(svc.greet('World')) // Hello, World!The library is split into focused entry points ensuring that each surface stays compatible with its specific toolchain requirements.
| Import path | Description |
|---|---|
@caffeine-projects/dicaf |
Core components. Start here. |
@caffeine-projects/dicaf/decorators |
ECMAScript decorators (stage 3). No tsconfig changes or extra dependencies required. |
@caffeine-projects/dicaf/decorators/legacy |
Legacy TypeScript decorators. Requires experimentalDecorators, emitDecoratorMetadata, and reflect-metadata. |
@caffeine-projects/dicaf/testing |
Testing utilities. |
@caffeine-projects/dicaf/internals |
Low-level decorator registrar. Intended for library/framework authors building on top of DiCaf. |
No additional dependencies or tsconfig flags required.
import { DiCaf } from '@caffeine-projects/dicaf'
import { Injectable, Extends } from '@caffeine-projects/dicaf/decorators'
abstract class Notifier {
abstract send(msg: string): void
}
@Injectable()
@Extends()
class EmailNotifier extends Notifier {
send(msg: string) {
console.log(`Email: ${msg}`)
}
}
const kRepository = Symbol('app:repository')
interface Repository {
save(item: string): void
}
@Injectable(kRepository)
class MySQLRepository implements Repository {
save(): void { ... }
}
@Injectable([Notifier, kRepository])
class OrderService {
constructor(readonly notifier: Notifier, readonly repository: Repository) {}
place(item: string) {
this.repository.save(item)
this.notifier.send(`Order placed: ${item}`)
}
}
const container = new DiCaf()
await container.init()
const orderService = container.get(OrderService)
orderService.place('Laptop')
// Email: Order placed: LaptopEnable in tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}Import reflect-metadata once at your entry point:
import 'reflect-metadata'
import { DiCaf } from '@caffeine-projects/dicaf'
import { Injectable, Extends } from '@caffeine-projects/dicaf/decorators/legacy'
abstract class Notifier {
abstract send(msg: string): void
}
@Injectable()
@Extends()
class EmailNotifier extends Notifier {
send(msg: string) {
console.log(`Email: ${msg}`)
}
}
const kRepository = Symbol('app:repository')
interface Repository {
save(item: string): void
}
@Injectable(kRepository)
class MySQLRepository implements Repository {
save(): void { ... }
}
@Injectable()
class OrderService {
constructor(readonly notifier: Notifier, @Inject(kRepository) readonly repository: Repository) {}
place(item: string) {
this.repository.save(item)
this.notifier.send(`Order placed: ${item}`)
}
}
const container = new DiCaf()
await container.init()
const orderService = container.get(OrderService)
orderService.place('Laptop')
// Email: Order placed: LaptopNo decorators, no tsconfig changes.
import { DiCaf } from '@caffeine-projects/dicaf'
abstract class Notifier {
abstract send(msg: string): void
}
class EmailNotifier extends Notifier {
send(msg: string) {
console.log(`Email: ${msg}`)
}
}
class OrderService {
constructor(readonly notifier: Notifier) {}
place(item: string) {
this.notifier.send(`Order placed: ${item}`)
}
}
const container = new DiCaf()
container.bind(EmailNotifier).toSelf().extends()
container.bind(OrderService).toSelf([Notifier])
await container.init()
const orderService = container.get(OrderService)
orderService.place('Laptop')
// Email: Order placed: LaptopFull documentation is available in the docs/ directory and on the documentation site:
Measured with mitata.
Resolution latency against a 7-class dependency graph.
| Benchmark | Latency |
|---|---|
Raw (new) |
9.64 ns |
| DiCaf (provider) | 158.11 ns |
| DiCaf | 165.17 ns |
| Awilix | 481.83 ns |
| injection-js | 569.86 ns |
| TSyringe | 1.15 µs |
| Inversify | 1.93 µs |
| TypeDI | 2.28 µs |
| LoopBack | 2.71 µs |
| NestJS | 4.91 µs |
| Benchmark | Latency |
|---|---|
| DiCaf (provider) | 16.37 ns |
| DiCaf | 17.85 ns |
| Awilix | 17.98 ns |
| TypeDI | 27.23 ns |
| Awilix (cradle) | 31.16 ns |
| injection-js | 40.21 ns |
| TSyringe | 68.56 ns |
| NestJS | 81.12 ns |
| Inversify | 101.72 ns |
| LoopBack | 195.07 ns |
Run the benchmarks locally with:
npm run bench:compare.
This project is MIT licensed.