This guide provides best practices, patterns, and recommendations for using EnvGuard Node.js runtime effectively and securely.
- Security Best Practices
- Development Patterns
- Environment Management
- Error Handling
- Testing
- Performance
- Migration from dotenv
- Troubleshooting
# ✅ Good - Template file (committed)
# .env.template
API_KEY=
DATABASE_URL=
# ❌ Bad - Actual secrets (DO NOT COMMIT!)
# .env
API_KEY=sk_live_abc123
DATABASE_URL=postgres://user:pass@host/dbAlways add to .gitignore:
.env
.env.local
.env.*.local
.envguard/// ✅ Good - Separate secrets per environment
// Development
envguard set API_KEY dev_key_123 --env development
// Production
envguard set API_KEY prod_key_456 --env production
// Load correct environment
import { load } from '@envguard/node';
await load({ environment: process.env.NODE_ENV });// ✅ Good - Validate on startup
import { load } from '@envguard/node';
try {
const result = await load({ validate: true });
if (!result.success) {
console.error('Missing required secrets:', result.errors);
process.exit(1);
}
} catch (error) {
console.error('Failed to load secrets:', error);
process.exit(1);
}
// ❌ Bad - No validation
require('@envguard/node/config');
// App continues with missing secrets!// ✅ Good - Handle keychain errors gracefully
import { load, KeychainError } from '@envguard/node';
try {
await load();
} catch (error) {
if (error instanceof KeychainError) {
console.error('Keychain access denied. Check system permissions.');
// Provide fallback or exit
}
throw error;
}# Update secret in keychain
envguard set API_KEY new_rotated_key
# Restart application to pick up new value
pm2 restart my-app// ✅ Best for simple apps
// index.js
require('@envguard/node/config');
// Secrets automatically loaded
console.log(process.env.API_KEY);// ✅ Best for complex apps
// src/config/secrets.ts
import { load } from '@envguard/node';
export async function initSecrets(): Promise<void> {
const result = await load({
environment: process.env.NODE_ENV,
validate: true,
debug: process.env.DEBUG === 'true',
});
if (!result.success) {
throw new Error(`Failed to load ${result.errors.length} required secrets`);
}
console.log(`Loaded ${result.count} secrets`);
}
// src/index.ts
import { initSecrets } from './config/secrets';
async function main() {
await initSecrets();
// Start your app
}
main().catch(console.error);// ✅ Best for libraries or testing
import { populate } from '@envguard/node';
// Get secrets without polluting process.env
const secrets = await populate({
environment: 'production',
});
// Use secrets directly
const apiClient = new APIClient({
apiKey: secrets.API_KEY,
baseURL: secrets.API_URL,
});// ✅ Load only in certain environments
import { load } from '@envguard/node';
if (process.env.NODE_ENV !== 'test') {
await load();
} else {
// Use test fixtures
process.env.API_KEY = 'test_key';
}ENVGUARD_ENV > NODE_ENV > 'development'
// Set explicit environment
process.env.ENVGUARD_ENV = 'staging';
// Load secrets for staging
await load(); // Uses 'staging'# Development
envguard set DATABASE_URL postgres://localhost/dev_db --env development
# Staging
envguard set DATABASE_URL postgres://staging.example.com/db --env staging
# Production
envguard set DATABASE_URL postgres://prod.example.com/db --env production// ✅ Good - Allow local overrides
import { load } from '@envguard/node';
await load({
override: true, // Override existing env vars
});
// Now process.env from shell takes precedence
// Useful for local development tweaks// ✅ Strict validation in production
import { load, isProduction } from '@envguard/node';
await load({
validate: isProduction(), // Only validate in production
debug: !isProduction(), // Debug in non-prod
});// ✅ Good - Provide fallbacks
import { load } from '@envguard/node';
try {
await load({ validate: false });
} catch (error) {
console.warn('Using fallback configuration');
// Fallback to safe defaults
process.env.LOG_LEVEL = 'info';
process.env.CACHE_TTL = '3600';
}// ✅ Good - Handle different error types
import {
load,
NotInitializedError,
ValidationError,
KeychainError,
} from '@envguard/node';
try {
await load();
} catch (error) {
if (error instanceof NotInitializedError) {
console.error('Run "envguard init" first');
process.exit(1);
}
if (error instanceof ValidationError) {
console.error('Missing secrets:', error.errors);
// List which secrets are missing
process.exit(1);
}
if (error instanceof KeychainError) {
console.error('Keychain access failed. Check permissions.');
process.exit(1);
}
throw error; // Unknown error
}// ✅ Good - Fail fast on startup
async function validateConfig(): Promise<void> {
const result = await load({ validate: true });
if (!result.success) {
console.error('Configuration errors:');
result.errors.forEach((err) => {
console.error(` ❌ ${err.key}: ${err.message}`);
});
throw new Error('Invalid configuration');
}
console.log(`✅ Loaded ${result.count} secrets`);
}// ✅ Good - Mock keychain in tests
import { load } from '@envguard/node';
import { createMockKeychain } from '@envguard/node/testing';
describe('App', () => {
it('should load secrets', async () => {
const mockKeychain = createMockKeychain({
API_KEY: 'test_key',
DATABASE_URL: 'test_db',
});
const result = await load({
keychain: mockKeychain,
validate: false,
});
expect(result.success).toBe(true);
expect(result.count).toBe(2);
});
});// ✅ Good - Clean environment per test
import { withCleanEnv, withReset } from '@envguard/node/testing';
describe('Config', () => {
it('should load in clean environment', async () => {
await withCleanEnv(async () => {
// Test with no env vars
await load();
expect(process.env.API_KEY).toBeDefined();
});
});
it('should reset after load', async () => {
await withReset(async () => {
await load();
// Auto-reset after test
});
});
});// ✅ Good - Test actual keychain (opt-in)
describe('Integration', () => {
// Skip in CI, run manually
it.skip('should load from real keychain', async () => {
const result = await load({
environment: 'test',
});
expect(result.success).toBe(true);
});
});// ✅ Good - Load secrets once at startup
let secretsLoaded = false;
async function ensureSecrets(): Promise<void> {
if (!secretsLoaded) {
await load();
secretsLoaded = true;
}
}
// Call once
await ensureSecrets();// ✅ Good - Load only when needed
let secrets: Record<string, string> | null = null;
async function getSecrets(): Promise<Record<string, string>> {
if (!secrets) {
const result = await populate();
secrets = result;
}
return secrets;
}// ✅ Good - Batch load
const result = await load(); // Loads all secrets once
// ❌ Bad - Multiple keychain calls
for (const key of keys) {
await keychain.get(key); // Slow!
}1. Install EnvGuard:
npm install @envguard/node
npm uninstall dotenv2. Initialize project:
npx envguard init3. Migrate secrets:
# Read from .env file
cat .env | while IFS='=' read -r key value; do
envguard set "$key" "$value"
done4. Update code (one line change!):
- require('dotenv/config');
+ require('@envguard/node/config');5. Delete .env file:
rm .env
git rm --cached .env 2>/dev/null || true6. Update .gitignore:
echo ".envguard/" >> .gitignore| dotenv | @envguard/node | Notes |
|---|---|---|
dotenv.config() |
envguard.config() |
✅ Compatible |
dotenv.parse(str) |
envguard.parse(str) |
|
| - | await envguard.load() |
🆕 Async version |
| - | await envguard.populate() |
🆕 Non-invasive |
| - | envguard.reset() |
🆕 Cleanup |
Cause: No .envguard/config.json file
Solution:
envguard initCause: Wrong environment or package name
Solution:
// Enable debug logging
await load({ debug: true });
// Check logs for:
// - Environment detected
// - Package name
// - Keys found in manifestCause: System keychain permissions
Solution (macOS):
# Grant Terminal/IDE access to keychain
# System Preferences → Privacy & Security → KeychainSolution (Linux):
# Install libsecret
sudo apt-get install libsecret-1-devCause: Tests accessing system keychain
Solution:
// Use MockKeychain in tests
import { createMockKeychain } from '@envguard/node/testing';
const result = await load({
keychain: createMockKeychain({ API_KEY: 'test' }),
});Enable detailed logging:
# Environment variable
export ENVGUARD_DEBUG=true
# Or programmatically
await load({ debug: true });Output:
[@envguard/node] Starting secret load process
[@envguard/node] Environment: production
[@envguard/node] Package: my-app
[@envguard/node] Found 5 keys in manifest
[@envguard/node] Resolved secret: API_KEY
[@envguard/node] Resolved secret: DATABASE_URL
[@envguard/node] Successfully resolved 5 secrets
[@envguard/node] Injection complete: 5 injected, 0 skipped
Questions? Open an issue or check the FAQ.