Basic Usage Example

This example demonstrates a complete Flagify integration in an Express.js application. It covers initialization, flag evaluation, and cleanup.

Setup

// flagify.ts
import { Flagify } from '@flagify/node';

export const flagify = new Flagify({
  projectKey: 'my-project',
  publicKey: process.env.FLAGIFY_PUBLIC_KEY!,
  options: {
    realtime: true,
  },
});

Wait for the initial sync before handling requests:

await flagify.ready();

Express middleware

Create a middleware that attaches flag evaluation to each request. For flags that don’t need per-user targeting, the cached isEnabled() / getValue() are fine. For flags with targeting rules, attach a per-request evaluate(flag) helper that forwards the request’s user.

// middleware/flags.ts
import { flagify } from '../flagify';
import type { Request, Response, NextFunction } from 'express';

export function flagsMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
) {
  req.flags = {
    // Cached, no user context — for global flags
    isEnabled: (flag: string) => flagify.isEnabled(flag),
    getValue: <T>(flag: string, fallback: T) => flagify.getValue<T>(flag, fallback),

    // Per-request, server-side targeting — for user-targeted flags
    evaluate: (flag: string) =>
      flagify.evaluate(flag, {
        id: req.user.id,
        role: req.user.role,
        email: req.user.email,
      }),
  };

  next();
}

Route handlers

Use flags in your route handlers. Read cached values for global flags, and await req.flags.evaluate() for user-targeted ones.

// routes/checkout.ts
app.get('/checkout', (req, res) => {
  const useNewCheckout = req.flags.isEnabled('new-checkout-flow');
  const maxItems = req.flags.getValue<number>('cart-max-items', 50);

  if (useNewCheckout) {
    res.render('checkout-v2', { maxItems });
  } else {
    res.render('checkout', { maxItems });
  }
});

// routes/admin.ts — flag with targeting rule "role equals admin"
app.get('/admin', async (req, res) => {
  const result = await req.flags.evaluate('admin-tools');
  if (!result.value) return res.status(403).end();
  res.render('admin');
});

Gradual rollout pattern

A common pattern is to gradually roll out a feature while monitoring metrics:

app.get('/api/search', (req, res) => {
  const useNewSearch = req.flags.isEnabled('new-search-engine');

  const startTime = Date.now();
  let results;

  if (useNewSearch) {
    results = await newSearchEngine.query(req.query.q);
  } else {
    results = await legacySearch.query(req.query.q);
  }

  const duration = Date.now() - startTime;

  // Track performance by variant for comparison
  metrics.record('search_latency', duration, {
    engine: useNewSearch ? 'new' : 'legacy',
  });

  res.json(results);
});

Graceful shutdown

Disconnect the realtime listener when the server shuts down:

process.on('SIGTERM', () => {
  flagify.destroy();
  server.close();
});