flaggle
Version:
Feature flag service for Node.js, NestJS, and React
216 lines (172 loc) • 6.03 kB
Markdown
by-step walkthrough for integrating the **flaggle** feature flag service into a NestJS application using a clean, modular, and idiomatic approach.
1. [Installation](
2. [Database Module Setup](
3. [Feature Flag Module Setup](
4. [Integrating into the Main App Module](
5. [Setting Up the Dashboard UI](
6. [Using FeatureFlagService in Your Application](
---
First, add **flaggle** and the required PostgreSQL driver to your project:
```bash
npm install flaggle pg
yarn add flaggle pg
```
Create a dedicated database module to manage the PostgreSQL connection and provide it globally.
**src/database.module.ts**
```ts
import { Module, Global } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Pool } from 'pg';
export const PG_POOL = 'PG_POOL';
@Global()
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('POSTGRES_HOST'),
port: configService.get('POSTGRES_PORT'),
username: configService.get('POSTGRES_USER'),
password: configService.get('POSTGRES_PASSWORD'),
database: configService.get('POSTGRES_DB'),
entities: ['dist/**/*.entity.js'],
synchronize: true, // Set to false in production
}),
}),
],
providers: [
{
provide: PG_POOL,
inject: [ConfigService],
useFactory: (configService: ConfigService) => new Pool({
host: configService.get('POSTGRES_HOST'),
port: configService.get('POSTGRES_PORT'),
user: configService.get('POSTGRES_USER'),
password: configService.get('POSTGRES_PASSWORD'),
database: configService.get('POSTGRES_DB'),
}),
},
],
exports: [PG_POOL, TypeOrmModule],
})
export class DatabaseModule {}
```
Encapsulate **flaggle** logic in a dynamic module.
**src/feature-flags/feature-flag.module.ts**
```ts
import { Global, Module, DynamicModule, ModuleMetadata } from '@nestjs/common';
import { FeatureFlagService, PostgresAdapter } from 'flaggle';
import { Pool } from 'pg';
export const FEATURE_FLAG_OPTIONS = 'FEATURE_FLAG_OPTIONS';
export interface FeatureFlagModuleOptions {
pool: Pool;
env: string;
tableName?: string;
autoCreate?: boolean;
}
export interface FeatureFlagModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
inject?: any[];
useFactory: (...args: any[]) => Promise<FeatureFlagModuleOptions> | FeatureFlagModuleOptions;
}
@Global()
@Module({})
export class FeatureFlagModule {
static forRootAsync(options: FeatureFlagModuleAsyncOptions): DynamicModule {
const optionsProvider = {
provide: FEATURE_FLAG_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
const serviceProvider = {
provide: FeatureFlagService,
inject: [FEATURE_FLAG_OPTIONS],
useFactory: async (flaggleOptions: FeatureFlagModuleOptions) => {
const adapter = new PostgresAdapter(flaggleOptions.pool, flaggleOptions.tableName, flaggleOptions.autoCreate);
await adapter.init();
return new FeatureFlagService(adapter, flaggleOptions.env);
},
};
return {
module: FeatureFlagModule,
imports: options.imports || [],
providers: [optionsProvider, serviceProvider],
exports: [serviceProvider],
};
}
}
```
Import the DatabaseModule and FeatureFlagModule in `app.module.ts`.
**src/app.module.ts**
```ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Pool } from 'pg';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DatabaseModule, PG_POOL } from './database.module';
import { FeatureFlagModule } from './feature-flags/feature-flag.module';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
DatabaseModule,
FeatureFlagModule.forRootAsync({
inject: [PG_POOL, ConfigService],
useFactory: (pool: Pool, configService: ConfigService) => ({
pool,
tableName: 'flaggle',
autoCreate: true,
env: configService.get<string>('NODE_ENV', 'dev'),
}),
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
```
**flaggle** provides a pre-built Dashboard.
**src/main.ts**
```ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { createFlaggleRouter, FeatureFlagService } from 'flaggle';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
app.setGlobalPrefix('v1');
const featureFlagService = app.get(FeatureFlagService);
const flaggleRouter = await createFlaggleRouter(featureFlagService);
app.use('/flaggle', flaggleRouter); // Dashboard at /v1/flaggle/dashboard
await app.listen(process.env.PORT || 3000);
}
bootstrap();
```
Inject `FeatureFlagService` into any service or controller:
**src/app.service.ts**
```ts
import { Injectable } from '@nestjs/common';
import { FeatureFlagService } from 'flaggle';
@Injectable()
export class AppService {
constructor(private readonly featureFlagService: FeatureFlagService) {}
async getHello(): Promise<string> {
if (await this.featureFlagService.isEnabled('new-greeting')) {
return 'Hello from the new feature!';
}
return 'Hello World!';
}
}
```
This guide provides a step-