Skip to content

feat: implement per-organization feature flag system#135

Open
Phantomcall wants to merge 1 commit into
Pidoko257:mainfrom
Phantomcall:feat/feature-flag-system
Open

feat: implement per-organization feature flag system#135
Phantomcall wants to merge 1 commit into
Pidoko257:mainfrom
Phantomcall:feat/feature-flag-system

Conversation

@Phantomcall

Copy link
Copy Markdown
Contributor

Per-Organization Feature Flag System

Problem

ProxyPay needs to roll out new features to specific organizations before general availability. Without a feature flag system, enabling/disabling features requires code changes and deployments, making gradual rollouts and targeted testing impractical.

Solution

Implement a feature flag system backed by PostgreSQL with Redis caching. Flags are stored per organization, cached in Redis with a 60-second TTL, and evaluated via Express middleware. An admin API enables/disables flags per organization without a deployment.

Changes

New Files

File Purpose
migrations/20260630_create_feature_flags_table.sql Creates feature_flags table with organization_id, flag_name, enabled, unique constraint, indexes, and auto-update trigger
src/services/featureFlagService.ts Core service: reads from Redis cache (with DB fallback), writes to DB + Redis, supports TTL-based cache invalidation
src/middleware/featureFlag.ts requireFeature('flag_name') middleware — resolves org from req.user or req.jwtUser, returns 403 on disabled flag
src/routes/featureFlags.ts Admin CRUD routes: GET / (list by org), PUT /:flagName (upsert), DELETE /:flagName (remove)

Modified Files

File Change
src/constants/errorCodes.ts Added FEATURE_NOT_ENABLED error code (403)
src/index.ts Added import and mount for feature flag admin routes at /api/admin/feature-flags

How It Works

  1. Storage: feature_flags table stores (organization_id, flag_name, enabled) with a unique constraint on the pair.
  2. Caching: On read, Redis key feature_flag:{orgId}:{flagName} is checked first (60s TTL). On cache miss, the DB is queried and the result is cached. Writes update both DB and Redis.
  3. Middleware: requireFeature('flag_name') is an Express middleware factory. It resolves the requesting organization from req.user.id (admin API key) or req.jwtUser.userId (user JWT). If the flag is disabled, returns 403 with ERR_FEATURE_NOT_ENABLED.
  4. Admin API:
    • GET /api/admin/feature-flags?organizationId=<id> — list all flags for an org
    • PUT /api/admin/feature-flags/:flagName — upsert a flag ({ organizationId, enabled })
    • DELETE /api/admin/feature-flags/:flagName — delete a flag
  5. TTL: Changes are reflected within 60 seconds (Redis cache TTL).

Acceptance Criteria

  • feature_flags table stores flag name, organization_id, and enabled boolean
  • Middleware function requireFeature('flag_name') checks the flag for the requesting org
  • Disabled feature endpoints return 403 with ERR_FEATURE_NOT_ENABLED
  • Admin endpoint manages flags per organization; changes reflected within 60 seconds (Redis TTL)

Usage Example

import { requireFeature } from "../middleware/featureFlag";

router.get(
  "/beta-feature",
  requireFeature("beta_dashboard"),
  async (req, res) => {
    // Only accessible when org has beta_dashboard enabled
    res.json({ data: "beta feature" });
  },
);
# Enable a feature flag
curl -X PUT /api/admin/feature-flags/beta_dashboard \
  -H "X-API-Key: <admin-key>" \
  -d '{"organizationId": "org-uuid", "enabled": true}'

closes #114

- Add feature_flags table migration with unique constraint
- Add FEATURE_NOT_ENABLED error code (403)
- Create featureFlagService with Redis caching (60s TTL)
- Create requireFeature('flag_name') middleware
- Add admin CRUD routes for managing flags per org

closes Pidoko257#114
@drips-wave

drips-wave Bot commented Jun 30, 2026

Copy link
Copy Markdown

@Phantomcall Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create Per-Organization Feature Flag System

1 participant