Feature Toggle Package
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
initialFlagsor env prefix - Programmatic checks – Inject
FeatureToggleServiceand useisEnabled(),set(),get() - Env integration – Use an env prefix (e.g.
FEATURE_) so vars likeFEATURE_NEW_CHECKOUTbecome 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
- FeatureToggleModule – Registers the service and optional
forRoot(options)for initial flags and env prefix - FeatureToggleService – Holds flags in memory;
isEnabled(name),get(name),set(name, value) - @FeatureToggle(name) – Method/class decorator that applies a guard; guard injects the service and returns
isEnabled(name) - 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?)
- initialFlags –
Record<string, boolean>: flags to set when the module loads. - envPrefix –
string: environment variable prefix. Any env var likePREFIX_NAMEis read and stored as a flag. The name is converted to camelCase (e.g.FEATURE_NEW_UI→newUi). Valuestrue,1,yes(case-insensitive) are treated astrue; otherwisefalse.
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;
undefinedif 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
@UseGuardsmanually.
Best practices
- Naming – Use clear, stable flag names (e.g.
newCheckout,betaApi). - Env – Use
envPrefixso you can turn flags on/off per environment without code changes. - Defaults – Unset flags are off; set
initialFlagsor env for features you want on by default. - Scoping – Prefer method-level
@FeatureTogglewhen only some routes need the flag; use class-level for whole controllers behind a flag.