Last active
May 13, 2021 14:52
-
-
Save evanhsu/676906d03dd6aadb8b2b5cf698f48aea to your computer and use it in GitHub Desktop.
A typescript config object that provides strong typing of environment variables for node.js projects
This file contains 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 { logger } from './util/logger'; | |
// appHealth keeps track of the success/error state of each critical component | |
// during app startup (database connection, env vars are present, remote API credentials work, etc). | |
// This info then supports the kubernetes health-check mechanism by preventing the app from simply | |
// crashing when it isn't configured correctly and instead it boots up and reports that it's degraded | |
// from its /healthz API endpoint. | |
// If you want to trim some complexity, you can eliminate appHealth and just *throw* an error instead | |
// of storing an error in the appHealth object. | |
import { appHealth } from './util/appHealth'; | |
// This is the typescript interface for your config object. | |
// If you need to add a new config value to your environment, you'll need to | |
// add it here AND add it to the schema object below. | |
// Yes, you need to add it in both places. No, I couldn't come up with any way around it. | |
// | |
// Any keys added to this type should also go in ../.env.example for documentation. | |
interface Configuration { | |
APOLLO_DEBUG_MODE: boolean; | |
JWT_SECRET: string; | |
MONGO_CONNECTION_URL: string; | |
SENDGRID_API_KEY: string; | |
} | |
function isValidConfig(env: any): env is Configuration { | |
// The schema object should conform to the Configuration interface above. | |
// We need this object in addition to the typescript interface to compare to | |
// the shape of the process.env object, which is dynamically loaded at runtime. | |
// There's no way to check that an object adheres to a Typescript interface at runtime. | |
const schema: Record<keyof Configuration, string> = { | |
APOLLO_DEBUG_MODE: '', | |
JWT_SECRET: '', | |
MONGO_CONNECTION_URL: '', | |
SENDGRID_API_KEY: '', | |
}; | |
const missingKeys = Object.keys(schema) | |
.filter((key) => env[key] === undefined) | |
.map((key) => key as keyof Configuration); | |
if (missingKeys.length > 0) { | |
const err = new Error( | |
`Configuration is missing some keys: ${missingKeys.join(', ')}. ` + | |
'Set values for these keys in the .env file (pre-prod) or env-secrets.yaml (prod).' | |
); | |
appHealth.configurationSuccess = false; | |
appHealth.recordError('Configuration is missing some keys', 'config'); | |
logger.error(err); | |
return false; | |
} | |
return true; | |
} | |
const getConfig = () => { | |
let config = process.env; | |
if (isValidConfig(config)) { | |
appHealth.configurationSuccess = true; | |
return config as Configuration; | |
} | |
return (process.env as unknown) as Configuration; | |
}; | |
// When you import config elsewhere, it will have properties | |
// that match your Configuration interface (defined above) | |
// i.e. config.MONGO_CONNECTION_URL | |
const config = getConfig(); | |
export { config }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment