Documentation
Keyed Services
Keyed Services
Group multiple implementations under a key and resolve the one you need.
Keyed services let you register multiple implementations of the same interface under a named group, then retrieve a specific one by key — or inject the whole group as a Map — without writing any factory boilerplate.
Registering keyed services
Programmatic API
import { ContainerBuilder, KeyedReference, KeyedGroupReference } from 'node-dependency-injection'
import StripePayment from './payments/StripePayment'
import PaypalPayment from './payments/PaypalPayment'
const container = new ContainerBuilder()
// Register implementations under the 'payment' group
container.registerKeyed('payment', 'stripe', StripePayment).setDefault(true)
container.registerKeyed('payment', 'paypal', PaypalPayment)
await container.compile()
// Retrieve by key
const stripe = container.getKeyed('payment', 'stripe')
// Retrieve the default (the one marked with .setDefault(true))
const def = container.getKeyed('payment')
// Get all implementations as a Map<key, instance>
const all = container.getKeyedGroup('payment')
YAML configuration
services:
payment.stripe:
class: 'payments/StripePayment'
keyed:
group: payment
key: stripe
default: true
payment.paypal:
class: 'payments/PaypalPayment'
keyed:
group: payment
key: paypal
Injecting keyed services
Injecting a specific key
Use KeyedReference (or @keyed(group, key) in YAML) to inject one specific implementation:
container.register('checkout', CheckoutService)
.addArgument(new KeyedReference('payment', 'stripe'))
checkout:
class: 'CheckoutService'
arguments: ['@keyed(payment, stripe)']
Injecting the whole group
Use KeyedGroupReference (or @keyed_group(group) in YAML) to receive a Map<string, T> with all registered implementations:
container.register('payment.router', PaymentRouter)
.addArgument(new KeyedGroupReference('payment'))
payment.router:
class: 'PaymentRouter'
arguments: ['@keyed_group(payment)']
Autowire integration
When using Autowire you can inject keyed services into typed TypeScript constructor parameters by registering a bind whose name matches the parameter name exactly.
// TypeScript services
export default class CheckoutService {
constructor(private readonly payment: IPaymentService) {}
}
export default class PaymentRouter {
constructor(private readonly payments: Map<string, IPaymentService>) {}
}
container.registerKeyed('payment', 'stripe', StripePaymentService)
container.registerKeyed('payment', 'paypal', PaypalPaymentService)
// Bind name must match the constructor parameter name
container.addBind('payment', new KeyedReference('payment', 'stripe'))
container.addBind('payments', new KeyedGroupReference('payment'))
const autowire = new Autowire(container)
await autowire.process()
await container.compile()
// container.get(CheckoutService).payment → StripePaymentService instance
// container.get(PaymentRouter).payments → Map { 'stripe' => …, 'paypal' => … }
Note: Named binds take priority over type-based resolution, so the same mechanism also works for scalars:
container.addBind('apiKey', '%env(API_KEY)%').
