Feature Flags
Relevant source files
The following files were used as context for generating this wiki page:
The Feature Flags system in the OnChain Sage backend provides a mechanism to dynamically enable or disable application features at runtime without requiring a new code deployment. This is particularly useful for rolling out new functionalities, A/B testing, or quickly disabling a feature if it causes issues.
The system is built around a central service that reads flag states from a JSON configuration file. A NestJS middleware then uses this service to protect specific API routes, blocking access to endpoints associated with a disabled feature. This modular design makes it straightforward to manage and extend feature toggling across the backend application.
Sources: apps/backend/src/config/feature-flag.ts, apps/backend/src/middleware/feature-flag.middleware.ts, ARCHITECTURE.md
Architecture and Components
The feature flag system consists of three main parts: a configuration file, a service, and a middleware. The lint-results.json file also indicates the existence of a controller for API-based management.
flags.json: A simple JSON file that acts as the source of truth for the state of all feature flags.FeatureFlagsService: An injectable NestJS service that reads, caches, and provides methods to check or modify the state of the flags.FeatureFlagMiddleware: A NestJS middleware that intercepts incoming requests, checks the relevant flag using theFeatureFlagsService, and either allows the request to proceed or returns a503 Service Unavailableerror.FeatureFlagsController: A controller (inferred from file listings) that likely exposes theFeatureFlagsServicemethods via an API, allowing for remote management of flags.
The following diagram illustrates the interaction between these components.
Configuration
The entire system is driven by a simple JSON configuration file.
flags.json
This file is located at apps/backend/config/flags.json and stores the state of all feature flags as a key-value map. The key is the flag's name, and the value is a boolean (true for enabled, false for disabled).
The service attempts to load this file on application startup. If the file is not found or is unreadable, the service falls back to a default configuration where signals_enabled and user_preferences_enabled are set to true.
An example flags.json based on the middleware implementation would look like this:
{
"signals_enabled": true,
"user_preferences_enabled": false
}
Sources: apps/backend/src/config/feature-flag.ts:9-13, apps/backend/src/config/feature-flag.ts:22-25
FeatureFlagsService
This service is the core of the feature flag system, responsible for all logic related to loading, checking, and updating flags. It is defined in apps/backend/src/config/feature-flag.ts.
Initialization
The service implements the OnModuleInit lifecycle hook from NestJS. During module initialization, it calls the loadFlags() method to read the flags.json file from the disk and load the flag states into a private flags property in memory.
// apps/backend/src/config/feature-flag.ts:15-17
async onModuleInit() {
await this.loadFlags();
}
If loading fails, it logs an error and sets a default state for the flags to ensure the application can continue running.
Sources: apps/backend/src/config/feature-flag.ts:15-27
Core Methods
The service exposes several public methods to interact with the feature flags.
| Method | Parameters | Returns | Description |
|---|---|---|---|
isEnabled | flagName: string | boolean | Returns true if the specified flag's value is true. |
setFlag | flagName: string, value: boolean | Promise<void> | Sets the value of a flag in memory and persists the change to flags.json. |
getAllFlags | (none) | Record<string, boolean> | Returns a copy of the current state of all flags. |
saveFlags | (none) | Promise<void> | (Private, called by setFlag) Writes the current in-memory flag states to the flags.json file. |
Sources: apps/backend/src/config/feature-flag.ts:29-50
FeatureFlagMiddleware
This middleware is responsible for enforcing the feature flags at the routing level. It intercepts incoming requests to specific endpoints and checks their corresponding flags.
Logic Flow
For each incoming request, the middleware performs the following steps:
- It retrieves the request path (e.g.,
/signals). - It checks if the path starts with a prefix that is protected by a feature flag.
- If it is a protected path, it calls
featureFlagsService.isEnabled()with the corresponding flag name. - If the flag is disabled, the middleware immediately ends the request-response cycle by sending a
503 Service Unavailableresponse. - If the flag is enabled or the path is not protected, it calls
next()to pass control to the next middleware or the route handler.
The following sequence diagram shows a request to a disabled feature being blocked.
Protected Routes
The middleware is configured to protect the following API routes:
| Route Path Prefix | Required Feature Flag |
|---|---|
/signals | signals_enabled |
/users/preferences | user_preferences_enabled |
Sources: apps/backend/src/middleware/feature-flag.middleware.ts:12-28
API Management
While the source code for the controller is not provided in the context, the file apps/backend/src/feature-flags/feature-flags.controller.ts is listed in the project's lint results. Based on NestJS conventions and the methods available in FeatureFlagsService, it is highly probable that this controller exposes endpoints for managing flags via a REST API.
These endpoints would likely allow an administrator to:
- View the status of all flags (e.g.,
GET /feature-flagscallinggetAllFlags()). - Enable or disable a specific flag (e.g.,
POST /feature-flagscallingsetFlag()).
This provides a way to toggle features on a running instance of the application without needing to manually edit the flags.json file and restart the server.
Sources: apps/backend/lint-results.json
Summary
The feature flag system is a simple yet powerful component of the OnChain Sage backend. It provides a clean separation of concerns, with a file-based configuration for persistence, a centralized service for business logic, and a middleware for enforcement at the API level. This architecture allows developers to control feature availability dynamically, improving the development lifecycle and operational stability.