Last active
March 28, 2020 15:15
-
-
Save reecelucas/a71b0cc0ebeee6da620cffc352eee1dc to your computer and use it in GitHub Desktop.
Basic feature flag implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default { | |
FEATURE_A: { | |
name: 'FEATURE_A', | |
description: 'Human readable description of feature A', | |
enabled: { | |
development: true, | |
production: false | |
} | |
}, | |
FEATURE_B: { | |
name: 'FEATURE_B', | |
description: 'Human readable description of feature B', | |
enabled: { | |
development: false, | |
production: true | |
} | |
} | |
}; | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import config from "./featureConfig"; | |
import Cookies from "js-cookie"; | |
const getDefaultConfigForEnv = environment => | |
Object.entries(config).reduce( | |
(obj, [key, value]) => ({ | |
...obj, | |
[key]: value.enabled[environment] | |
}), | |
{} | |
); | |
const getConfigOverridesFromCookie = cookieString => { | |
try { | |
return cookieString ? JSON.parse(cookieString) : {}; | |
} catch (error) { | |
throw new Error( | |
"Could not determine feature config overrides: error parsing cookie value" | |
); | |
} | |
}; | |
const getConfig = (environment, cookieString) => { | |
const defaultConfig = getDefaultConfigForEnv(environment); | |
const overrides = getConfigOverridesFromCookie(cookieString); | |
return { | |
...defaultConfig, | |
...overrides | |
}; | |
}; | |
export const getFeatureUtils = ({ environment, cookieName }) => { | |
const cookieValueFromDocument = Cookies.get(cookieName); | |
const isFeatureEnabled = (featureName, cookieString) => { | |
// If `cookieString` is provided we use this to determine feature overrides. | |
// This is useful when using `isFeatureEnabled` on the server, where `cookieName` can | |
// be accessed on the `request` object. On the client we retrieve the cookie value from | |
// the document. | |
const cookieValue = cookieString || cookieValueFromDocument; | |
const config = getConfig(environment, cookieValue); | |
const value = config[featureName]; | |
if (value === undefined) { | |
throw new Error( | |
`${featureName} is not defined for the ${environment} environment` | |
); | |
} | |
return value; | |
}; | |
// `values` should be an object. E.g. setFeatureOverrides({ FEATURE_A: false }) | |
const setFeatureOverrides = values => { | |
Cookies.set(cookieName, values); | |
}; | |
const getFeatureConfig = cookieString => { | |
const cookieValue = cookieString || cookieValueFromDocument; | |
return getConfig(environment, cookieValue); | |
}; | |
return { isFeatureEnabled, setFeatureOverrides, getFeatureConfig }; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getFeatureUtils } from "./featureFlags"; | |
const { | |
isFeatureEnabled, | |
getFeatureConfig, | |
setFeatureOverrides | |
} = getFeatureUtils({ | |
environment: "development", | |
cookieName: "featureOverrides" | |
}); | |
console.log(isFeatureEnabled("FEATURE_B")); // false | |
console.log(getFeatureConfig()); // { FEATURE_A: true, FEATURE_B: false } | |
setFeatureOverrides({ FEATURE_B: true }); | |
// After refreshing page... | |
console.log(isFeatureEnabled("FEATURE_B")); // true | |
console.log(getFeatureConfig()); // { FEATURE_A: true, FEATURE_B: true } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import express from "express"; | |
import cookieParser from "cookie-parser"; | |
import { getFeatureUtils } from "./featureFlags"; | |
const app = express(); | |
const { isFeatureEnabled, getFeatureConfig } = getFeatureUtils({ | |
environment: "development", | |
cookieName: "featureOverrides", | |
}); | |
const ensureFeatureUtils = (req, _res, next) => { | |
if (!req.cookies) { | |
throw new Error( | |
"Server configuration error: ensureFeatureUtils middleware requires cookie-parser middleware upstream" | |
); | |
} | |
const { featureOverrides } = req.cookies; | |
req.isFeatureEnabled = (featureName) => isFeatureEnabled(featureName, featureOverrides); | |
req.getFeatureConfig = () => getFeatureConfig(featureOverrides); | |
next(); | |
}; | |
app.use(cookieParser()); | |
app.use(ensureFeatureUtils); | |
app.get("/", (req, res) => { | |
console.log(req.getFeatureConfig()); // { FEATURE_A: true, FEATURE_B: false } | |
if (req.isFeatureEnabled("FEATURE_A")) { | |
res.send("FEATURE_A is enabled"); | |
} else { | |
res.send("FEATURE_A is NOT enabled"); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment