This is a submission for the DevCycle Feature Flag Challenge: Feature Flag Funhouse
Do you love coffee? As developers, many of us jokingly claim to be "powered by coffee", and the thought of opening a quaint coffee shop someday often lingers in the back of our minds — perhaps as a post-dev career dream.
When brainstorming ideas for a fun project to showcase feature flags, this coffee shop fantasy kept nudging me: "Pick me! Build me!". So, I decided to indulge that thought and create Brew Haven, a dummy coffee shop app that explores the power of feature flags.
What I Built
As you might have guessed, I built a playground to explore the power of feature flags, packaged as a coffee shop app. The app features customizable menus, seasonal items, and dynamic A/B testing for promotions using the DevCycle SDK. It also leverages the DevCycle Management APIs to power an admin panel, giving shop managers full control over these features in real time.
Demo
You can try out the live app here: Brew Haven
The admin page uses a dummy password auth. Use
admin@123
passowrd to view the admin panel and play around with the available feature flags.
Video Demo
The following video gives a short walkthrough of the app.
Showcasing Feature Flags in Brew Haven
1. The Coffee Menu
The shop menu is customizable and evolves with the seasons—thanks to feature flags. The Coffee Menu feature flag consists of 3 variables that control:
- Showing nutritional info for health-conscious customers.
- Enabling the seasonal menu for limited-time offerings.
- Allowing order customization for that perfect latte.
You can choose from 3 different variations of the flag:
- Basic: Disables all menu variables.
- Standard: Allows order customization and shows nutrition info.
- Seasonal: Enables all three variables.
2. Payment and Ordering
This feature flag makes the ordering process adaptable. Depending on the selected Payment and Ordering flag variation, admins can:
- Allow online payments.
- Enable loyalty points redemption.
- Enable live order tracking.
This flag also consists of 3 variables to control the above aspects, and the following three variations:
- Basic: Disables all the variables.
- Standard: Allows online payments and live order tracking.
- Premium: Allows loyalty points apart from other standard features.
3. A/B Testing with Promotions
The app also demonstrates A/B testing through a promotions feature flag, which allows admins to:
- Offer discounts to a random subset of customers.
- Customize promotion details, like discount percentages/amount and minimum cart values to avail the promotions.
This feature flag consists of four variables that allows setting:
- The promotion text (string).
- Discount value (number).
- Discount type (enum with values none, amount, percentage).
- Min cart value (number, 0 for no min cart value).
These feature flags made it easy to bring my coffee shop dreams closer to reality, blending fun experimentation with real-world use cases like A/B testing and customizations.
App Screenshots
Home Page
Menu Page
Checkout Page
Admin Page
My Code
The source code of the app is open source, and is available on GitHub.
ra-jeev / brew-haven
Dummy Coffee Shop App with DevCycle Feature Flags
Brew Haven ☕
Project Overview
Brew Haven is a dynamic coffee shop web application that demonstrates the use of feature flags using DevCycle. The app provides a simple customizable menu, checkout experience, and an innovative admin interface for managing feature variations and A/B testing.
Key Features
- Home page showcasing the coffee shop
- Interactive menu display
- Dummy checkout process
- Feature flag management through DevCycle
- Admin page for feature flag control
Technologies Used
- React
- Vite
- Shadcn UI
- DevCycle SDK
- Netlify Functions
Prerequisites
- Node.js (recommended version 18+)
- pnpm package manager
- DevCycle account
- Netlify account and CLI (for local development and deployment)
Environment Setup
- Clone the repository
- Install dependencies:
pnpm install
- Rename
.env.example
to.env
and update the following environment variables:
VITE_DEVCYCLE_CLIENT_SDK_KEY=your_client_sdk_key
DEVCYCLE_API_CLIENT_ID=your_client_id
DEVCYCLE_API_CLIENT_SECRET=your_client_secret
DEVCYCLE_PROJECT_ID=your_project_id
Local Development
To run the application in development mode:
# For standard development
pnpm dev
# For testing admin page with Netlify Functions
netlify dev
Deployment
The project is…
Technologies Used
To bring Brew Haven to life, I used the following:
- React with Vite for a fast and modular frontend.
- Shadcn UI for styling and components.
- Netlify Functions for secure serverless DevCycle Management API calls.
- DevCycle SDK for feature flag integration on the client side.
Client Side Usage of DevCycle SDK
After adding the DevCycle React SDK
dependency, we first initialize the DevCycleProvider
in the App.tsx
file as shown below:
// src/App.tsx
import { withDevCycleProvider } from "@devcycle/react-client-sdk";
// ...
function App() {
return (
<ThemeProvider defaultTheme="system" storageKey="coffee-shop-ui-theme">
<Router>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/menu" element={<Menu />} />
<Route path="/checkout" element={<Checkout />} />
<Route path="/orders" element={<Orders />} />
<Route
path="/admin"
element={
<ProtectedRoute>
<Admin />
</ProtectedRoute>
}
/>
</Routes>
</Layout>
</Router>
<Toaster />
</ThemeProvider>
);
}
export default withDevCycleProvider({
sdkKey: import.meta.env.VITE_DEVCYCLE_CLIENT_SDK_KEY,
options: {
logLevel: "debug",
},
})(App);
And then we create a useFeatureFlags
hook to get the various feature flags variables associated with the app as shown below:
// src/hooks/use-feature-flags.ts
import { useVariableValue } from "@devcycle/react-client-sdk";
import { featureKeys } from "@/lib/consts";
export function useFeatureFlags() {
const showNutritionInfo = useVariableValue(
featureKeys.SHOW_NUTRITION_INFO,
false,
);
const enableOnlinePayment = useVariableValue(
featureKeys.ENABLE_ONLINE_PAYMENT,
false,
);
const showPromotionalBanner = useVariableValue(
featureKeys.SHOW_PROMOTIONAL_BANNER,
"",
);
// etc.
return {
showNutritionInfo,
enableOnlinePayment,
showPromotionalBanner,
// ...
};
}
Interacting with DevCycle Management APIs
For securely calling the Management APIs, we use Netlify serverless functions so that the DevCycle API's client_id
and client_secret
are not exposed to the client.
Before we can interact with the DevCycle APIs, we need to generate an auth token using the DevCycle APIs client id and secret. The below code shows how to generate the auth token:
// netlify/functions/feature-flags.mts
interface AuthToken {
access_token: string;
expires_in: number;
token_type: string;
}
let tokenCache: { token: string; expiresAt: number } | null = null;
async function getAuthToken() {
if (tokenCache && tokenCache.expiresAt > Date.now()) {
return tokenCache.token;
}
try {
const response = await fetch("https://auth.devcycle.com/oauth/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "client_credentials",
audience: "https://api.devcycle.com/",
client_id: process.env.DEVCYCLE_API_CLIENT_ID!,
client_secret: process.env.DEVCYCLE_API_CLIENT_SECRET!,
}),
});
if (!response.ok) {
throw new Error(`Auth failed: ${response.status}`);
}
const data: AuthToken = await response.json();
tokenCache = {
token: data.access_token,
expiresAt: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
};
return data.access_token;
} catch (error) {
console.error("Failed to get auth token:", error);
throw error;
}
}
Now we can fetch the available feature flags, and their config (to get the currently active variation) using the below code:
// netlify/functions/feature-flags.mts
interface Distribution {
_variation: string;
percentage: number;
}
interface FeatureConfig {
_feature: string;
_environment: string;
status: string;
targets: {
_id: string;
name: string;
distribution: Distribution[];
audience: {
name: string;
filters: {
operator: "and" | "or";
filters: { type: string }[];
};
};
}[];
}
async function getFeatures(
featuresUrl: string,
headers: Record<string, string>,
) {
const featuresResponse = await fetch(featuresUrl, { headers });
if (!featuresResponse.ok) {
throw new Error(`Failed to fetch flags: ${featuresResponse.status}`);
}
const featuresData = await featuresResponse.json();
// Fetch the config for each of the feature flags
// We're only fetching the configs for the development env
const configPromises = featuresData.map(async (feature: any) => {
const configResponse = await fetch(
`${featuresUrl}/${feature._id}/configurations?environment=development`,
{ headers },
);
if (!configResponse.ok) {
console.error(`Failed to fetch config for feature ${feature._id}`);
return null;
}
const configs: FeatureConfig[] = await configResponse.json();
return {
...feature,
targets: configs[0].targets,
status: configs[0].status,
};
});
const featuresWithConfigs = await Promise.all(configPromises);
return featuresWithConfigs.filter((f) => f !== null);
}
To update the feature flags config (changing the variation, or toggle the flag altogether), we can use the below function:
async function updateFeature(
featuresUrl: string,
featureId: string,
headers: Record<string, string>,
update: any,
) {
const response = await fetch(
`${featuresUrl}/${featureId}/configurations?environment=development`,
{
method: "PATCH",
headers,
body: JSON.stringify(update),
},
);
if (!response.ok) {
throw new Error(`Failed to update feature: ${response.status}`);
}
return await response.json();
}
Finally, here is the Netlify function that uses the above functions to serve the client requests:
export default async (req: Request) => {
try {
const { method } = req;
const token = await getAuthToken();
const featuresBaseUrl = `https://api.devcycle.com/v1/projects/${process.env.DEVCYCLE_PROJECT_ID}/features`;
const headers = {
Authorization: `Bearer ${token}`,
};
if (method === "GET") {
const features = await getFeatures(featuresBaseUrl, headers);
return Response.json({ features });
}
if (method === "PATCH") {
const body = await req.json();
const { featureId, update } = body;
const data = await updateFeature(
featuresBaseUrl,
featureId,
{
...headers,
"Content-Type": "application/json",
},
update,
);
return Response.json({ data });
}
return new Response(JSON.stringify({ error: "Method not allowed" }), {
status: 405,
headers: {
"Content-Type": "application/json",
},
});
} catch (error) {
console.error("Error:", error);
return new Response(
JSON.stringify({
error: error instanceof Error ? error.message : "Internal server error",
}),
{
status: 500,
headers: {
"Content-Type": "application/json",
},
},
);
}
};
This Netlify function is called by the client in the following way:
// src/lib/api.ts
export async function getFeatureFlags() {
try {
const response = await fetch("/.netlify/functions/feature-flags");
if (!response.ok) {
throw new Error("Failed to fetch flags");
}
const data = await response.json();
return { success: true, data: data.features as Feature[] };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
}
// and, so on...
The above code snippets capture how the DevCycle SDK
and its Management APIs
are used within the app. You can go through the shared source code to view the complete implementation in more detail.
My DevCycle Experience
This was a fun project to build, thanks to the challenge prompt that said something about "fun". After the initial small learning curve of using the DevCycle dashboard — to create feature flags, adding variables and understanding how variations work — things were quite smooth.
The only issue I faced was with the onboarding tutorial app. The DevCycle web console got stuck in some state like "Waiting for the application launch" with the next button disabled. I had to ultimately login from an incognito browser window to get to the dashboard. But, doing this skipped the tutorial, and I had to figure out the tutorial code working (the feature flags creation etc) on my own as the project readme doesn't provide much info on this.
Additional Prize Categories
API All-Star
Wrapping Up
Brew Haven isn’t just a coffee shop app — it’s a showcase of how feature flags can make your projects more dynamic, responsive, and fun.
Feature flags allow you to:
- Do gradual rollouts.
- Reduce deployment risks.
- Enable rapid experimentation.
- Personalize user experiences.
Next time you’re sipping coffee and dreaming big, think about how feature flags can brew innovation into your projects. ☕
Until next time.
Keep adding the bits, and soon you'll have a lot of bytes to share with the world.
Top comments (0)