Feature Flags in Frontend Applications

Feature Flags in Frontend Applications

Feature Flag Usage in Frontend Applications: A Comprehensive Overview

Introduction

Feature flags, also known as feature toggles, are an essential technique for modern software development, particularly in frontend applications. They enable teams to release new features gradually, test them with select users, and turn them on or off dynamically without needing to redeploy the application. This technique plays a vital role in continuous integration/continuous delivery (CI/CD) pipelines, A/B testing, canary releases, and controlled feature rollouts.

In this article, we will explore five popular feature flag solutions used in frontend applications, comparing their benefits and trade-offs. We will also propose a solution that aims to simplify the integration of feature flags in modern development workflows in particular for the frontend applications.

Existing Solutions for Feature Flags in Frontend Applications

1. LaunchDarkly

LaunchDarkly is one of the most popular feature flagging platforms, offering robust, enterprise-grade tools for managing feature flags at scale. It provides a centralized control panel to manage flags, extensive SDK support (including JavaScript, TypeScript, React, etc.), and real-time flag updates.

Key Features:

  • Feature flag management for large-scale environments
  • Easy-to-use dashboard with granular control
  • Real-time updates for flags
  • A/B testing and experimentation tools
  • Advanced targeting and segmentation of users

Trade-offs:

  • Expensive for small teams or startups
  • Requires an external API call for feature flag evaluations, which might add latency

2. Unleash

Unleash is an open-source feature management solution built for flexibility and extensibility. With a focus on self-hosting, it allows organizations to manage feature flags without depending on third-party services. Unleash offers a powerful UI to manage features and has SDKs for various languages, including JavaScript and React.

Key Features:

  • Open-source and highly customizable
  • Multi-environment support
  • Self-hosted, with no reliance on external services
  • Fine-grained targeting based on user segments, IP addresses, and environments
  • SDK support for both frontend and backend

Trade-offs:

  • Setup and maintenance overhead for self-hosting
  • Requires integration effort compared to fully managed solutions

3. ConfigCat

ConfigCat is a cloud-based feature flagging service designed with simplicity in mind. It offers a global CDN for fast flag evaluations and is relatively straightforward to integrate with frontend applications. It is also highly focused on performance, ensuring near-instant flag evaluations.

Key Features:

  • Intuitive and easy-to-use interface
  • Real-time flag toggling
  • SDKs available for multiple frontend frameworks
  • Affordable pricing for small teams
  • Integrations with CI/CD pipelines

Trade-offs:

  • Limited to basic targeting rules compared to competitors like LaunchDarkly
  • Does not support more complex rollout strategies such as multivariate testing

4. Flagsmith

Flagsmith is an open-source feature flag and remote configuration platform with the option to self-host or use a managed cloud service. It provides robust support for experimentation and segmentation, making it ideal for frontend-heavy applications.

Key Features:

  • Open-source with both self-hosted and managed options
  • Multi-region deployments for reduced latency
  • Fine-grained control over user segments and environments
  • Custom rules and targeting strategies
  • Remote configuration capabilities

Trade-offs:

  • Requires more configuration and setup compared to managed solutions like ConfigCat
  • Slightly steeper learning curve for advanced use cases

5. Split.io

Split.io is another feature experimentation platform designed to provide powerful control over feature releases, A/B testing, and experimentation. It offers real-time performance monitoring, ensuring that users can track how feature releases impact user experience and performance.

Key Features:

  • Advanced experimentation and A/B testing capabilities
  • Real-time performance and impact monitoring
  • Integration with popular analytics tools
  • Fast, real-time flag evaluations
  • SDKs for JavaScript, React, and other frontend technologies

Trade-offs:

  • Overkill for simple feature toggling requirements
  • Pricing may be a barrier for smaller teams
  • Setup can be complex for teams unfamiliar with experimentation tools

Solution for Feature Flags in Frontend Applications Using Providers Approach

The following code implements a provider-based architecture for managing feature flags in frontend applications. This solution allows for different storage mechanisms (providers) to be used for managing feature flags, such as Local Storage, Cookies, HTTP Headers, and OpenFeature-based remote services (e.g., flagd). The goal is to provide flexibility in managing feature flags across different environments and to simplify switching between different storage or flag management mechanisms.

Key Components

  1. Provider Factory (createProviderInstance):
    • The createProviderInstance function is a factory that instantiates the appropriate feature flag provider based on the input provider type.
    • Supported providers include:
      • Local Storage (LocalStorageFeatureFlagProvider)
      • Cookies (CookieFeatureFlagProvider)
      • HTTP Headers (HttpHeaderFeatureFlagProvider)
      • OpenFeature (e.g., flagd, via OpenFeatureFlagdFeatureFlagProvider)
  2. Base Feature Flag Provider (BaseFeatureFlagProvider):
    • This class defines the basic structure and methods that all feature flag providers should follow.
    • It provides default implementations of methods such as getFlag(), setFeatureFlag(), componentMountCallback(), and componentUnmountCallback().
  3. Individual Providers: Each provider is a specialized class that extends BaseFeatureFlagProvider and provides specific logic for interacting with different storage mechanisms.

    • Local Storage (LocalStorageFeatureFlagProvider): Uses browser local storage to store and retrieve feature flag values.
    • Cookies (CookieFeatureFlagProvider): Checks browser cookies to manage feature flags.
    • HTTP Headers (HttpHeaderFeatureFlagProvider): Reads HTTP headers from the server response to determine feature flags.
    • OpenFeature Provider (OpenFeatureFlagdFeatureFlagProvider): Integrates with a remote feature flag service (e.g., flagd) using the OpenFeature SDK.

Example Workflow

  1. Choosing a Provider: The createProviderInstance function allows the application to choose the appropriate provider dynamically based on configuration or environment. For example:
    • Use local storage in a development environment.
    • Use cookies for a quick flag toggle without persistent storage.
    • Use HTTP headers or a remote service (via OpenFeature) in production to manage feature flags centrally.
  2. Feature Flag Retrieval: Each provider implements the getFlag() method, which returns a boolean indicating whether a particular feature is enabled or not. Depending on the provider:
    • Local Storage: Retrieves the flag from localStorage.
    • Cookies: Parses the flag from the browser’s cookies.
    • HTTP Headers: Extracts the flag from server-sent HTTP headers.
    • OpenFeature: Retrieves the flag from a remote service using the OpenFeature SDK.
  3. Component Lifecycle: For more dynamic flag management (e.g., remote services like OpenFeature), providers such as OpenFeatureFlagdFeatureFlagProvider can hook into the component lifecycle, triggering callbacks when the flag service becomes ready or is updated.

Code Explanation

1. createProviderInstance

This factory function creates instances of the specific providers based on the string name of the provider.

1
2
3
4
5
6
7
8
9
10
11
12
export const createProviderInstance = (feature: string, provider: string, options?: string): BaseFeatureFlagProvider => {
  if ('local storage' === provider.toLocaleLowerCase()) {
    return new LocalStorageFeatureFlagProvider(feature);
  } else if ('cookie' === provider.toLocaleLowerCase()) {
    return new CookieFeatureFlagProvider(feature);
  } else if ('http-header' === provider.toLocaleLowerCase()) {
    return new HttpHeaderFeatureFlagProvider(feature);
  } else if (provider.startsWith('openfeature|')) {
    return createOpenFeatureProviderInstance(feature, provider, options || '');
  }
  return new BaseFeatureFlagProvider();
}

This function supports:

  • Local Storage: "local storage"
  • Cookies: "cookie"
  • HTTP Headers: "http-header"
  • OpenFeature-based providers (e.g., flagd): openfeature|flagd
2. BaseFeatureFlagProvider

This base class provides default implementations for the feature flag interface and can be extended by other providers. By default, all flags are disabled.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export class BaseFeatureFlagProvider {

  getFlag(): boolean {
    return false;
  }

  setFeatureFlag(enabled: boolean): void {
    return;
  }

  componentMountCallback(providerReadyCallback: EventCallback): void {
    return;
  }

  componentUnmountCallback(providerReadyCallback: EventCallback): void {
    return;
  }

}
3. LocalStorageFeatureFlagProvider

This provider uses browser local storage to persist and retrieve feature flags.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export class LocalStorageFeatureFlagProvider extends BaseFeatureFlagProvider {
  private feature: string = '';

  constructor(feature: string) {
    super();
    this.feature = feature;
  }

  private getLocalStorageKeyName(): string {
    return `${LOCAL_STORAGE_KEY_PREFIX}/${this.feature}`;
  }

  getFlag(): boolean {
    const item = localStorage.getItem(this.getLocalStorageKeyName());
    return 'true' === item?.toLocaleLowerCase();
  }

  setFeatureFlag(enabled: boolean): void {
    localStorage.setItem(this.getLocalStorageKeyName(), enabled.toString());
  }
}
4. CookieFeatureFlagProvider

This provider checks browser cookies to determine if a feature is enabled.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export class CookieFeatureFlagProvider extends BaseFeatureFlagProvider {
  private feature: string = '';

  constructor(feature: string) {
    super();
    this.feature = feature;
  }

  getFlag(): boolean {
    const match = document.cookie.match(new RegExp('(^| )' + COOKIE_NAME + '=([^;]+)'));
    if (match) {
      const features = match[2].split('|');
      return features.includes(this.feature);
    }
    return false;
  }
}
5. HttpHeaderFeatureFlagProvider

This provider reads the HTTP response headers from the server to determine feature flags.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export class HttpHeaderFeatureFlagProvider extends BaseFeatureFlagProvider {
  private feature: string = '';

  constructor(feature: string) {
    super();
    this.feature = feature;
  }

  getFlag(): boolean {
    updateWindowHttpHeaders();
    const featuresString = window.__feature_flags_http_headers__?.['x-feature-flags'];
    const features = featuresString?.split('|') || [];
    return features.includes(this.feature);
  }
}
6. OpenFeatureFlagdFeatureFlagProvider

This provider integrates with a remote service like flagd through the OpenFeature SDK.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
export class OpenFeatureFlagdFeatureFlagProvider extends BaseFeatureFlagProvider {
  static providers: Providers = {};
  private feature: string = '';
  private options: string = '';
  private clientName: string = '';

  constructor(feature: string, options: string) {
    super();
    this.feature = feature;
    this.options = options;
    this.updateOpenfeatureProvider();
  }

  private updateOpenfeatureProvider(): void {
    if ((this.options !== '') && (!OpenFeatureFlagdFeatureFlagProvider.providers[this.clientName])) {
      const uri = new URL(this.options);
      const openfeatureProviderFlagd = new FlagdWebProvider({
        host: uri.hostname,
        port: Number(uri.port),
        tls: uri.protocol === 'https',
        maxRetries: 3
      });
      OpenFeatureFlagdFeatureFlagProvider.providers[this.clientName] = openfeatureProviderFlagd;
      OpenFeature.setProvider(this.clientName, openfeatureProviderFlagd);
    }
  }

  getFlag(): boolean {
    const client = OpenFeature.getClient(this.clientName);
    return client.getBooleanValue(this.feature, false);
  }

  componentMountCallback(providerReadyCallback: EventCallback): void {
    OpenFeature.addHandler(ProviderEvents.Ready, providerReadyCallback);
  }

  componentUnmountCallback(providerReadyCallback: EventCallback): void {
    OpenFeature.removeHandler(ProviderEvents.Ready, providerReadyCallback);
  }
}

The full source code could be found here on my Github: https://github.com/sgdreamer7/lit-preact-feature-flag

Conclusion

This solution provides a flexible and extensible framework for managing feature flags in frontend applications. By using a provider-based approach, it enables seamless integration with different storage mechanisms like local storage, cookies, HTTP headers, and remote services like flagd through OpenFeature. This allows developers to manage feature flags in a scalable and adaptable manner depending on the needs of the application environment.