Typesafe Config is a library that allows you get your configuration in a typesafe way. It will get your configuration from muiltiple sources, merge them together and validate them against your schema provided.
- Get and merge configuration data from multiple sources
- Environment variables loader
- JSON file loader
- AWS Secrets Manager loader
- AWS Parameter Store loader
- Validate configuration data against a schema and retur the validated data with the types inferred
- Helper function to get the list of full qualified names used by the K/V loaders (env, AWS SM)
- Support zod schemas
- Add support for other schema validators (io-ts, yup, etc)
Installing the core package give you the basic functions and loaders to get your configuration data.
npm install @typesafe-config/core
We use nx to manage our monorepo and run our tests. you can run just the affected tests or the tests for a specific package:
npm run affected:test
or
npm run test core --watch ...
import { z } from 'zod';
import { resolve } from 'path';
import { createConfig, environmentVariablesLoader, jsonFileLoader } from '@typesafe-config/core';
const baseDir = resolve(__dirname, './config');
const config = await createConfig(
'zod',
z.object({
db: z.object({ url: z.string() }),
port: z.number(),
nested: z.object({ foo: z.object({ bar: z.string() }) }),
}),
[
// Load the file default.json or default.json5 from the baseDir
jsonFileLoader(baseDir, 'default'),
// Load specific configs for the current environment
jsonFileLoader(baseDir, process.env.NODE_ENV || 'development'),
// Last overrides from environment variables
environmentVariablesLoader(process.env),
],
);
// Now you can use your configuration data with the types inferred
console.log(config.db.url);
//...
If you just use sync loaders (custom loaders, or the environmentVariablesLoader) the return of the createConfig will not have a promise, so it's not necessary to await.
import { z } from 'zod';
import { resolve } from 'path';
import { createConfig, environmentVariablesLoader } from '@typesafe-config/core';
const config = await createConfig(
'zod',
z.object({
db: z.object({ url: z.string() }),
port: z.number(),
nested: z.object({ foo: z.object({ bar: z.string() }) }),
}),
[environmentVariablesLoader(process.env)],
{
onValidationError: (error, dataLoaded) => {
// You will receive the error from the schema validation library and the data loaded so far
},
// If you pass an onDataloaderError function, the createConfig will not throw an error if one of the loaders fail
// and will try to validate the data that was possible to load.
onDataLoaderError: (error: Error) => {
// You will receive the error from the data loader
},
},
);
import { z } from 'zod';
import { resolve } from 'path';
import { getConfigKeysInfo, environmentVariablesLoader } from '@typesafe-config/core';
const dir = resolve(__dirname, './config');
const info = await getConfigKeysInfo(
'zod',
z.object({
db: z.object({ url: z.string() }),
port: z.number(),
nested: z.object({ foo: z.object({ bar: z.string() }) }),
}),
[
// you can pass the loaders to get the information of what loaders was used for each key
// It's important to pass the loaders in the same order that you pass to the createConfig function
jsonFileLoader(dir, 'default'),
environmentVariablesLoader(process.env),
],
);
console.log(info);
// [
// { key: 'DB_URL', loader: 'environmentVariablesLoader', path: ['db', 'url'] },
// {
// key: 'PORT',
// loader: 'jsonFileLoader(/your/config/directory, [default])',
// path: ['port'],
// },
// {
// key: 'NESTED_FOO_BAR',
// loader: 'jsonFileLoader(/your/config/directory, [default])',
// path: ['nested', 'foo', 'bar'],
// },
// ]
}