node-env-resolver-aws
v13.0.0
Published
AWS resolvers for node-env-resolver (Secrets Manager, SSM Parameter Store)
Maintainers
Readme
node-env-resolver/aws
AWS integration for node-env-resolver with Secrets Manager and SSM Parameter Store support.
Install
npm install node-env-resolver/awsQuick start
One-line convenience functions (recommended)
import { resolveSsm, resolveSecrets } from 'node-env-resolver-aws';
// Resolve from SSM Parameter Store
const config = await resolveSsm(
{
path: '/myapp/config',
},
{
API_ENDPOINT: string(),
TIMEOUT: 30,
}
);
// Resolve from Secrets Manager
const secrets = await resolveSecrets(
{
secretId: 'myapp/production/secrets',
},
{
DATABASE_URL: string(),
API_KEY: string(),
}
);Using with resolveAsync() (for combining multiple sources)
import { resolveAsync } from 'node-env-resolver';
import { awsSecrets, awsSsm } from 'node-env-resolver-aws';
const config = await resolveAsync({
resolvers: [
[
awsSecrets({ secretId: 'myapp/production/secrets' }),
{
DATABASE_URL: postgres(),
API_KEY: string(),
},
],
[
awsSsm({ path: '/myapp/config' }),
{
TIMEOUT: 30,
},
],
],
});Features
- One-line convenience functions for quick setup
- Load secrets from AWS Secrets Manager
- Load parameters from SSM Parameter Store
- Secret reference handlers for URI-style dereferencing (
aws-sm://,aws-ssm://) - Safe (non-throwing) versions of all functions
- Automatic AWS credential detection (environment variables, IAM roles, ~/.aws/credentials)
- Optional TTL caching
- Full TypeScript support
Secret Reference Handlers
Use URI-style references in your .env files instead of plaintext secrets:
# .env
DATABASE_URL=aws-sm://prod/database/url
API_KEY=aws-sm://prod/api/key#token
REDIS_URL=aws-ssm:///prod/redis/urlimport { resolveAsync } from 'node-env-resolver';
import { createAwsSecretHandler, createAwsSsmHandler } from 'node-env-resolver-aws';
const config = await resolveAsync({
resolvers: [[dotenv(), schema]],
references: {
handlers: {
'aws-sm': createAwsSecretHandler({ region: 'us-east-1' }),
'aws-ssm': createAwsSsmHandler({ region: 'us-east-1' }),
},
},
});Secret Reference Formats
AWS Secrets Manager:
aws-sm://secret-id # Entire secret as string
aws-sm://secret-id#key # Extract JSON key from secretSSM Parameter Store:
aws-ssm:///path/to/parameter # Parameter valuePre-configured Handlers
import { awsSecretHandler, awsSsmHandler } from 'node-env-resolver-aws';
// Uses default AWS credentials chain (env vars, IAM roles, ~/.aws/credentials)
const config = await resolveAsync({
resolvers: [[dotenv(), schema]],
references: {
handlers: {
'aws-sm': awsSecretHandler,
'aws-ssm': awsSsmHandler,
},
},
});JSON Key Extraction
For secrets stored as JSON objects in AWS Secrets Manager:
# Secret "prod/database" contains: {"host": "db.example.com", "password": "secret123"}
DB_HOST=aws-sm://prod/database#host
DB_PASSWORD=aws-sm://prod/database#passwordHandler Options
const handler = createAwsSecretHandler({
region: 'us-east-1', // AWS region (default: process.env.AWS_REGION)
accessKeyId: 'AKIAIOSF...', // Explicit credentials (optional)
secretAccessKey: 'wJalrX...', // Explicit credentials (optional)
sessionToken: '...', // Temporary credentials (optional)
parseJson: true, // Parse JSON secrets (default: true)
});
const ssmHandler = createAwsSsmHandler({
region: 'us-east-1',
withDecryption: true, // Decrypt secure strings (default: true)
});Performance and caching
AWS API calls are slow (~100-200ms) and cost money. Caching is essential for production applications.
Without caching (not recommended)
// Every request hits AWS = slow and expensive
export const handler = async (event) => {
const config = await resolveSecrets(
{
secretId: 'myapp/secrets',
},
{
DATABASE_URL: postgres(),
API_KEY: string(),
}
);
// 200ms delay on EVERY request + AWS API costs
};With caching (recommended)
import { resolveAsync, cached, TTL } from 'node-env-resolver';
import { awsSecrets } from 'node-env-resolver-aws';
// Cache AWS calls - call resolveAsync() every time, let cached() make it fast
export const getConfig = async () => {
return await resolveAsync({
resolvers: [
[
cached(awsSecrets({ secretId: 'myapp/secrets' }), {
ttl: TTL.minutes5,
maxAge: TTL.hour,
staleWhileRevalidate: true,
}),
{
DATABASE_URL: postgres(),
API_KEY: string(),
},
],
],
});
};
// Call in your handler
export const handler = async (event) => {
const config = await getConfig(); // Fast after first call!
// Use config...
};AWS Lambda:
import { resolveAsync, cached, TTL } from 'node-env-resolver';
import { awsSecrets } from 'node-env-resolver-aws';
const getConfig = async () => {
return await resolveAsync({
resolvers: [
[
cached(awsSecrets({ secretId: 'myapp/lambda' }), {
ttl: TTL.minutes5,
staleWhileRevalidate: true,
}),
{
DATABASE_URL: postgres(),
},
],
],
});
};
export const handler = async (event) => {
const config = await getConfig(); // Call every invocation
// Lambda container reuse = cache persists across invocations
// = most invocations get instant config
};Best Practices
- Always use
cached()wrapper when accessing AWS Secrets Manager or SSM - Call
resolveAsync()every time - don't cache the result in a variable - Use
staleWhileRevalidate: truefor zero-latency updates - Choose appropriate TTL based on how often secrets change:
- Frequently rotating:
TTL.minutes5 - Rarely changing:
TTL.hourorTTL.hours6
- Frequently rotating:
- Set
maxAgeas a safety net (default: 1 hour)
AWS Credentials
This package uses the standard AWS SDK credential provider chain. Credentials are automatically detected from:
Environment variables (recommended for local development)
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY export AWS_REGION=us-west-2IAM roles (recommended for production - EC2, Lambda, ECS)
AWS credentials file (
~/.aws/credentials)Explicit options (for special cases):
await resolveSsm( { path: '/myapp/config', region: 'us-east-1', accessKeyId: 'AKIAIOSFODNN7EXAMPLE', secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', }, { API_ENDPOINT: string() } );
In most cases, you don't need to pass credentials explicitly - just set the standard AWS environment variables or use IAM roles.
API Functions
Convenience Functions
resolveSsm(ssmOptions, schema, resolveOptions?)
Directly resolve environment variables from SSM Parameter Store.
import { resolveSsm } from 'node-env-resolver-aws';
const config = await resolveSsm(
{
path: '/myapp/config',
region: 'us-east-1',
recursive: true,
},
{
API_ENDPOINT: string(),
TIMEOUT: 30,
}
);safeResolveSsm(ssmOptions, schema, resolveOptions?)
Safe version that returns a result object instead of throwing.
import { safeResolveSsm } from 'node-env-resolver-aws';
const result = await safeResolveSsm(
{
path: '/myapp/config',
},
{
API_ENDPOINT: string(),
}
);
if (result.success) {
console.log(result.data.API_ENDPOINT);
} else {
console.error(result.error);
}resolveSecrets(secretsOptions, schema, resolveOptions?)
Directly resolve environment variables from Secrets Manager.
import { resolveSecrets } from 'node-env-resolver-aws';
const config = await resolveSecrets(
{
secretId: 'myapp/production/secrets',
region: 'us-east-1',
},
{
DATABASE_URL: string(),
API_KEY: string(),
}
);safeResolveSecrets(secretsOptions, schema, resolveOptions?)
Safe version that returns a result object instead of throwing.
import { safeResolveSecrets } from 'node-env-resolver-aws';
const result = await safeResolveSecrets(
{
secretId: 'myapp/secrets',
},
{
DATABASE_URL: string(),
}
);
if (result.success) {
console.log(result.data.DATABASE_URL);
} else {
console.error(result.error);
}Resolver Functions (for use with resolveAsync())
awsSsm(options) and awsSecrets(options)
These return resolver objects for use with resolveAsync() when you need to combine multiple sources.
AWS Secrets Manager
Load JSON secrets from Secrets Manager:
import { resolveAsync } from 'node-env-resolver';
import { awsSecrets } from 'node-env-resolver-aws';
const config = await resolveAsync({
resolvers: [
[
awsSecrets({ secretId: 'myapp/secrets' }),
{
DATABASE_URL: postgres(),
API_KEY: string(),
},
],
],
});With options:
awsSecrets({
secretId: 'myapp/production/database',
region: 'us-east-1',
parseJson: true, // Default: true
});SSM Parameter Store
Load parameters from Parameter Store:
import { resolveAsync } from 'node-env-resolver';
import { awsSsm } from 'node-env-resolver-aws';
const config = await resolveAsync({
resolvers: [
[
awsSsm({ path: '/myapp/config' }),
{
API_ENDPOINT: string(),
TIMEOUT: 30,
},
],
],
});Get all parameters under a path:
awsSsm({
path: '/myapp/production',
region: 'us-west-2',
recursive: true,
});Caching
Add TTL caching to reduce AWS API calls:
import { resolveAsync, cached, TTL } from 'node-env-resolver';
import { awsSecrets } from 'node-env-resolver-aws';
const config = await resolveAsync({
resolvers: [
[
cached(awsSecrets({ secretId: 'myapp/secrets' }), {
ttl: TTL.minutes5,
maxAge: TTL.hour,
staleWhileRevalidate: true,
}),
{
DATABASE_URL: postgres(),
},
],
],
});AWS Permissions
Secrets Manager
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": ["arn:aws:secretsmanager:region:account:secret:myapp/secrets-*"]
}
]
}SSM Parameter Store
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"],
"Resource": ["arn:aws:ssm:region:account:parameter/myapp/*"]
}
]
}Configuration
awsSecrets options
interface AwsSecretsOptions {
secretId: string; // Secret ID or ARN
region?: string; // AWS region (overrides profile/env defaults)
accessKeyId?: string; // AWS access key (optional)
secretAccessKey?: string; // AWS secret key (optional)
profile?: string; // AWS shared credentials profile (optional)
parseJson?: boolean; // Parse JSON secrets (default: true)
}awsSsm options
interface AwsSsmOptions {
path: string; // Parameter path
region?: string; // AWS region (overrides profile/env defaults)
accessKeyId?: string; // AWS access key (optional)
secretAccessKey?: string; // AWS secret key (optional)
profile?: string; // AWS shared credentials profile (optional)
recursive?: boolean; // Get all parameters under path (default: false)
}Examples
Production app with one-line functions
import { resolveSecrets, resolveSsm } from 'node-env-resolver-aws';
// Load secrets from Secrets Manager
const secrets = await resolveSecrets(
{
secretId: 'myapp/production/secrets',
},
{
DATABASE_URL: postgres(),
JWT_SECRET: string(),
}
);
// Load config from SSM
const config = await resolveSsm(
{
path: '/myapp/production/config',
recursive: true,
},
{
NODE_ENV: ['development', 'production'] as const,
PORT: 3000,
}
);Production app with resolveAsync() (combining sources)
import { resolveAsync, processEnv } from 'node-env-resolver';
import { awsSecrets, awsSsm } from 'node-env-resolver-aws';
const config = await resolveAsync({
resolvers: [
[
processEnv(),
{
NODE_ENV: ['development', 'production'] as const,
PORT: 3000,
},
],
[
awsSecrets({ secretId: 'myapp/production/secrets' }),
{
DATABASE_URL: postgres(),
JWT_SECRET: string(),
},
],
[
awsSsm({ path: '/myapp/production/config' }),
{
TIMEOUT: 30,
},
],
],
});Lambda function
import { resolveSecrets } from 'node-env-resolver-aws';
const config = await resolveSecrets(
{
secretId: 'lambda/secrets',
},
{
API_ENDPOINT: string(),
TIMEOUT: 30,
}
);
export const handler = async (event) => {
// Use config
};Safe error handling
import { safeResolveSecrets } from 'node-env-resolver-aws';
const result = await safeResolveSecrets(
{
secretId: 'myapp/production/secrets',
},
{
DATABASE_URL: string(),
API_KEY: string(),
}
);
if (result.success) {
// Use result.data with full type safety
await connectDatabase(result.data.DATABASE_URL);
} else {
// Handle error gracefully
console.error('Failed to load secrets:', result.error);
process.exit(1);
}Error handling
With safe functions (recommended)
import { safeResolveSecrets } from 'node-env-resolver-aws';
const result = await safeResolveSecrets(
{
secretId: 'myapp/secrets',
},
{
DATABASE_URL: string(),
}
);
if (!result.success) {
console.error('Failed to load secrets:', result.error);
process.exit(1);
}
// Use result.data safely
console.log(result.data.DATABASE_URL);With try-catch
import { resolveSecrets } from 'node-env-resolver-aws';
try {
const config = await resolveSecrets(
{
secretId: 'myapp/secrets',
},
{
DATABASE_URL: string(),
}
);
} catch (error) {
console.error('Failed to load secrets from AWS:', error);
}Troubleshooting
Permission denied
- Check IAM permissions match examples above
- Verify resource ARNs are correct
Secret not found
- Verify secret ID or path exists
- Check region matches where secret is stored
Network timeout
- Check VPC/security group configuration
- Verify internet/NAT gateway access
Licence
MIT
