Skip to content

Instantly share code, notes, and snippets.

@evanhsu
Last active May 13, 2021 14:52
Show Gist options
  • Save evanhsu/676906d03dd6aadb8b2b5cf698f48aea to your computer and use it in GitHub Desktop.
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
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