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)%').

Related guides