Feature Toggle Package

npm downloads

The @hazeljs/feature-toggle package adds feature flags to your HazelJS app: protect routes with one decorator or branch in code. Flags are stored in memory and can be seeded from options or environment variables (e.g. FEATURE_NEW_UI).

Purpose

You need to ship or test features behind a switch, guard routes until a feature is ready, or run A/B code paths. The @hazeljs/feature-toggle package provides:

  • Decorator-first API@FeatureToggle('name') on a controller or method; disabled flag returns 403
  • In-memory store – No external service; optional seed from initialFlags or env prefix
  • Programmatic checks – Inject FeatureToggleService and use isEnabled(), set(), get()
  • Env integration – Use an env prefix (e.g. FEATURE_) so vars like FEATURE_NEW_CHECKOUT become flags

Architecture

graph TD
  A["Module forRoot<br/>(initialFlags, envPrefix)"] --> B["FeatureToggleService<br/>(in-memory Map)"]
  B --> C["@FeatureToggle decorator"]
  B --> D["Programmatic isEnabled/set/get"]
  C --> E["Guard per flag<br/>(canActivate)"]
  E --> B

Key components

  1. FeatureToggleModule – Registers the service and optional forRoot(options) for initial flags and env prefix
  2. FeatureToggleService – Holds flags in memory; isEnabled(name), get(name), set(name, value)
  3. @FeatureToggle(name) – Method/class decorator that applies a guard; guard injects the service and returns isEnabled(name)
  4. Guard – One guard class per feature name (cached); when the flag is off, the framework returns 403

Installation

npm install @hazeljs/feature-toggle

Quick Start

Register the module

import { HazelModule } from '@hazeljs/core';
import { FeatureToggleModule } from '@hazeljs/feature-toggle';

@HazelModule({
  imports: [
    FeatureToggleModule.forRoot({
      initialFlags: { newCheckout: true },
      envPrefix: 'FEATURE_',
    }),
  ],
})
export class AppModule {}

Protect routes with the decorator

import { Controller, Get } from '@hazeljs/core';
import { FeatureToggle } from '@hazeljs/feature-toggle';

@Controller('checkout')
export class CheckoutController {
  @Get('new')
  @FeatureToggle('newCheckout')
  getNewCheckout() {
    return { flow: 'new' };
  }

  @Get('legacy')
  getLegacyCheckout() {
    return { flow: 'legacy' };
  }
}

Apply the decorator on a class to require the flag for all routes:

@Controller('beta')
@FeatureToggle('betaApi')
export class BetaController {
  @Get()
  index() {
    return { message: 'Beta API' };
  }
}

Use in services (programmatic)

import { Service } from '@hazeljs/core';
import { FeatureToggleService } from '@hazeljs/feature-toggle';

@Service()
export class OrderService {
  constructor(private readonly featureToggle: FeatureToggleService) {}

  createOrder(data: OrderData) {
    if (this.featureToggle.isEnabled('newCheckout')) {
      return this.createWithNewFlow(data);
    }
    return this.createWithLegacyFlow(data);
  }
}

Module options

forRoot(options?)

  • initialFlagsRecord<string, boolean>: flags to set when the module loads.
  • envPrefixstring: environment variable prefix. Any env var like PREFIX_NAME is read and stored as a flag. The name is converted to camelCase (e.g. FEATURE_NEW_UInewUi). Values true, 1, yes (case-insensitive) are treated as true; otherwise false.

Example:

FeatureToggleModule.forRoot({
  initialFlags: { newCheckout: true, betaApi: false },
  envPrefix: 'FEATURE_',
});

With env:

FEATURE_NEW_CHECKOUT=true
FEATURE_BETA_API=0
FEATURE_NEW_UI=yes

FeatureToggleService API

  • isEnabled(name: string): boolean – Returns whether the flag is on. Unset flags are treated as disabled (false).
  • get(name: string): boolean | undefined – Raw value; undefined if the flag was never set.
  • set(name: string, value: boolean): void – Set or override a flag at runtime (in-memory only).

Decorator behavior

  • @FeatureToggle(featureName: string) – Use on a controller class or on a route method. Composes with the framework’s guard system: when the flag is disabled, the request is rejected (403) and the handler is not executed. No need to use @UseGuards manually.

Best practices

  1. Naming – Use clear, stable flag names (e.g. newCheckout, betaApi).
  2. Env – Use envPrefix so you can turn flags on/off per environment without code changes.
  3. Defaults – Unset flags are off; set initialFlags or env for features you want on by default.
  4. Scoping – Prefer method-level @FeatureToggle when only some routes need the flag; use class-level for whole controllers behind a flag.

What's next?

  • Use Config for other environment-driven settings
  • Explore Auth for route protection with roles
  • Check Cache for caching feature-flagged responses