Skip to main content

Getting Started

Installation

npm install @caffeine-projects/dicaf

DiCaf requires Node.js 20 or later and ships as an ES module with full CommonJS support.

Your first container

The core concept in DiCaf is a container: an object that knows how to create and wire up your application's dependencies. You tell the container what exists and how to build it; the container handles construction order and instance reuse.

import { DiCaf } from '@caffeine-projects/dicaf'

class Logger {
log(msg: string) {
console.log(msg)
}
}

class UserService {
constructor(private readonly logger: Logger) {}

greet(name: string) {
this.logger.log(`Hello, ${name}!`)
}
}

const di = new DiCaf()

di.bind(Logger).toSelf()
di.bind(UserService).toClass(UserService, [Logger])

await di.init()

const svc = di.get(UserService)
svc.greet('world') // Hello, world!

There are three things happening here:

  1. Bind — you tell the container what to register with bind().toClass().
  2. Init — calling await di.init() resolves the startup order and creates eager singletons.
  3. Getdi.get(UserService) returns the fully-constructed instance.
note

await di.init() must be called before di.get(). Skipping it means dependencies are not yet resolved.

Named keys

Class references are the most common key type, but you can use strings and symbols too. This is useful when you bind an interface or an abstract class — since those do not exist at runtime, a symbol acts as the token.

const kLogger = Symbol.for('logger')

di.bind(kLogger).toClass(Logger)

await di.init()

const logger = di.get<Logger>(kLogger)

Using decorators

For larger applications, declaring dependencies inline for every class becomes noisy. DiCaf provides a decorator API that co-locates dependency metadata with the class itself.

You need experimentalDecorators and emitDecoratorMetadata in your tsconfig.json, or a build tool that handles TC39 stage 3 decorators.

import { DiCaf } from '@caffeine-projects/dicaf'
import { Injectable } from '@caffeine-projects/dicaf/decorators'

@Injectable()
class Logger {
log(msg: string) {
console.log(msg)
}
}

@Injectable()
class UserService {
constructor(private readonly logger: Logger) {}

greet(name: string) {
this.logger.log(`Hello, ${name}!`)
}
}

const di = new DiCaf()
// @Injectable classes are picked up automatically
await di.init()

const svc = di.get(UserService)
svc.greet('world')

The container calls autoWire() in its constructor when decorators: true (the default), so every @Injectable class is registered before init().

Next steps

  • Modules — composing bindings with module functions
  • Decorators — full decorator reference and patterns
  • Scopes — controlling instance lifetime
  • Testing — isolated containers for unit and integration tests