npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nestjs-strategy

v2.0.0

Published

Strategy pattern registries for NestJS providers, selected by key

Readme

nestjs-strategy

The strategy pattern for NestJS. Name interchangeable providers with @Strategy(key), register them as a group, and inject one registry that selects the implementation by key at runtime.

npm version npm monthly downloads CI

Install

pnpm add nestjs-strategy

Requires Node.js 24+ and NestJS 12. Ships ESM, CommonJS, and TypeScript declarations.

Define and select strategies

Declare the group and its keys once with defineStrategyGroup(). The keys become literal types, so the editor autocompletes them in @Strategy, defaultKey and registry.get(), and a misspelled key is a compile error:

import {
  BadRequestException,
  Inject,
  Injectable,
  Module,
} from "@nestjs/common";
import {
  defineStrategyGroup,
  StrategyModule,
  type StrategyKeyOf,
  type StrategyRegistry,
} from "nestjs-strategy";

export interface PaymentStrategy {
  pay(amount: number): Promise<string>;
}

export const payment = defineStrategyGroup("payment", ["card", "bank"]);
export type PaymentMethod = StrategyKeyOf<typeof payment>; // "card" | "bank"

@payment.Strategy("card")
@Injectable()
export class CardPayment implements PaymentStrategy {
  async pay(amount: number) {
    return `charged ${amount} to the card`;
  }
}

@payment.Strategy("bank")
@Injectable()
export class BankTransfer implements PaymentStrategy {
  async pay(amount: number) {
    return `requested a transfer of ${amount}`;
  }
}

@Injectable()
export class CheckoutService {
  constructor(
    @Inject(payment.token)
    private readonly payments: StrategyRegistry<PaymentStrategy, PaymentMethod>,
  ) {}

  checkout(method: PaymentMethod, amount: number) {
    return this.payments.get(method).pay(amount);
  }

  checkoutRequest(method: string, amount: number) {
    // has() narrows an untrusted string to PaymentMethod.
    if (!this.payments.has(method)) {
      throw new BadRequestException(`Unsupported payment method: ${method}`);
    }
    return this.checkout(method, amount);
  }
}

@Module({
  imports: [
    StrategyModule.register({
      group: payment,
      strategies: [CardPayment, BankTransfer],
      defaultKey: "card",
    }),
  ],
  providers: [CheckoutService],
})
export class CheckoutModule {}

StrategyModule.register() instantiates every strategy through Nest dependency injection and exports a StrategyRegistry under payment.token. With a group, registration also requires exactly one strategy for every declared key, so payments.get(method) with a PaymentMethod never throws UnknownStrategyError. Adding a payment method means declaring its key and adding a decorated class to strategies; CheckoutService does not change.

Keys without a group

@Strategy(key) and StrategyModule.register({ name, strategies }) accept any string key. The registry is then a StrategyRegistry<PaymentStrategy> with string keys, injected with @Inject(getStrategyRegistryToken("payment")):

@Strategy("card")
@Injectable()
export class CardPayment implements PaymentStrategy {
  async pay(amount: number) {
    return `charged ${amount} to the card`;
  }
}

StrategyModule.register({
  name: "payment",
  strategies: [CardPayment, BankTransfer],
  defaultKey: "card",
});

Registry API

| Member | Behavior | | ---------------------------- | ---------------------------------------------------------------------------------------------------------- | | get(key) | Returns the strategy registered for key. Throws UnknownStrategyError for an unregistered key. | | get() | Returns the defaultKey strategy. Throws UnknownStrategyError when the group has no default. | | find(key) | Returns the strategy for key, or undefined. Accepts any string; declared keys are still autocompleted. | | has(key) | Reports whether key is registered and narrows key to the registry's key type. | | keys(), values() | Keys and strategies in registration order. | | entries(), iteration | [key, strategy] pairs in registration order; for (const [key, strategy] of registry) works directly. | | size, name, defaultKey | Group size, group name and default key. |

StrategyRegistry<T, K> types the strategies as T and the keys as K. K defaults to string; pass StrategyKeyOf<typeof group> for a declared group.

UnknownStrategyError exposes group, key and registeredKeys, and its message lists the registered keys. Map it to an HTTP error in your controller or exception filter when the key comes from a request.

To select by capability instead of by key, iterate over the strategies:

const strategy = registry
  .values()
  .find((candidate) => candidate.supports(order));

Dependencies of strategies

Strategies are ordinary providers and may inject other providers. List the modules that export those providers in imports:

StrategyModule.register({
  group: payment,
  strategies: [CardPayment, BankTransfer],
  imports: [PaymentGatewayModule],
});

Each registration creates its own strategy instances. Nest lifecycle hooks such as onModuleInit and onModuleDestroy run on them like on any other provider.

Multiple strategy groups

Register one module per group. Groups are isolated: the same key, or even the same class, can appear in several groups, and each registration has its own instances and registry.

export const shipping = defineStrategyGroup("shipping", [
  "standard",
  "express",
]);

@Module({
  imports: [
    StrategyModule.register({
      group: payment,
      strategies: [CardPayment, BankTransfer],
    }),
    StrategyModule.register({
      group: shipping,
      strategies: [StandardShipping, ExpressShipping],
      defaultKey: "standard",
    }),
  ],
})
export class OrdersModule {}

Inject each registry with @Inject(payment.token) or @Inject(shipping.token); for groups registered by name, use @Inject(getStrategyRegistryToken(name)). Use a distinct name for every group injected together. Set global: true to make a group's registry injectable everywhere.

Validation

StrategyModule.register() throws before the application bootstraps when:

  • name is empty or strategies is empty;
  • a class has no @Strategy() key of its own (keys are not inherited from a parent class);
  • two strategies use the same key;
  • defaultKey is not a registered key;
  • with a group, a strategy uses a key the group does not declare, or a declared key has no strategy.

@Strategy("") throws immediately. defineStrategyGroup() throws for an empty name, no keys, an empty key or a duplicated key, and group.Strategy(key) throws for an undeclared key passed from untyped code.

Comparison with Spring

In Spring, beans that implement a strategy interface are injected together as Map<String, Strategy> keyed by bean name. This package follows the same model for Nest providers:

| Spring | nestjs-strategy | | ----------------------------------------------- | ---------------------------------------------------------------- | | @Component("card") on an implementation | @payment.Strategy("card") on a provider class | | Component scanning of the interface's beans | StrategyModule.register({ name, strategies }) | | Injected Map<String, PaymentStrategy> | Injected StrategyRegistry<PaymentStrategy> | | map.get(key), map.getOrDefault(key, …) | registry.get(key), registry.get() with defaultKey | | Injected List<PaymentStrategy> + supports() | registry.values().find((strategy) => strategy.supports(input)) |

TypeScript interfaces do not exist at runtime, so a group is identified by its name instead of by the interface type.

API

| API | Purpose | | ---------------------------------- | --------------------------------------------------- | | defineStrategyGroup(name, keys) | Declare a group whose keys are literal types. | | group.Strategy(key) | Name a provider class with one of the group's keys. | | group.token | Injection token of the group's StrategyRegistry. | | StrategyKeyOf<typeof group> | Union of the group's keys. | | @Strategy(key) | Name a provider class as a strategy. | | StrategyModule.register(options) | Register a strategy group and export its registry. | | getStrategyRegistryToken(name) | Injection token of a group's StrategyRegistry. | | StrategyRegistry<T> | Select strategies by key. | | UnknownStrategyError | Error for an unregistered key or a missing default. | | getStrategyKey(target) | Read the key a class declares with @Strategy(). |

Contributing