@envguard/node
v0.2.0
Published
EnvGuard Node.js runtime - Drop-in dotenv replacement with OS keychain
Downloads
14
Maintainers
Readme
@envguard/node
Secure, local-first secret management for Node.js - Drop-in replacement for dotenv that stores secrets in OS keychain instead of
.envfiles.
Table of Contents
- Features
- Quick Start
- Installation
- Usage
- API Reference
- Migration from dotenv
- Environment Management
- Testing
- Documentation
- Examples
- FAQ
- Contributing
- License
Features
✨ Drop-in dotenv replacement - Change one line of code 🔐 OS keychain storage - macOS Keychain, Windows Credential Manager, Linux Secret Service 🌍 Multi-environment - Separate secrets for dev/staging/prod ✅ TypeScript-first - Full type safety and IntelliSense 🎯 Zero dependencies - Bundles all dependencies ⚡ Fast - <100ms startup time 🧪 Testing utilities - Mock keychain included 📦 ESM & CJS - Works everywhere
Quick Start
1. Install
npm install @envguard/node
# Or with yarn/pnpm
yarn add @envguard/node
pnpm add @envguard/node2. Initialize (one-time)
# Initialize EnvGuard in your project
npx envguard init
# Set your secrets
npx envguard set API_KEY your_secret_key_here
npx envguard set DATABASE_URL postgres://localhost/mydb3. Use in your app
Before (dotenv):
require('dotenv').config();After (EnvGuard):
require('@envguard/node/config');That's it! Your secrets now come from the OS keychain instead of .env files.
Installation
npm
npm install @envguard/nodeyarn
yarn add @envguard/nodepnpm
pnpm add @envguard/nodeRequirements
- Node.js >= 18.0.0
- Operating System: macOS, Windows, or Linux
Usage
Auto-Loading
The simplest way - automatically load secrets on import:
CommonJS
// Load secrets before anything else
require('@envguard/node/config');
// Secrets are now available
console.log(process.env.API_KEY);
console.log(process.env.DATABASE_URL);
const app = require('./app');
app.start();ES Modules
// Load secrets first
import '@envguard/node/config';
// Then import your app
import app from './app.js';
app.start();Programmatic API
For more control over loading:
import { load } from '@envguard/node';
async function main() {
// Load secrets
const result = await load({
environment: 'production', // Override environment
validate: true, // Validate required secrets
debug: true, // Enable debug logging
});
if (!result.success) {
console.error('Failed to load secrets:', result.errors);
process.exit(1);
}
console.log(`Loaded ${result.count} secrets`);
// Start your application
startApp();
}
main().catch(console.error);Node.js --require Hook
Load secrets before your application starts:
node --require @envguard/node/register app.jsEnvironment variables:
# Set environment
ENVGUARD_ENV=production node --require @envguard/node/register app.js
# Enable debug logging
ENVGUARD_DEBUG=true node --require @envguard/node/register app.jsAPI Reference
async config(options?: LoadOptions): Promise<LoadResult>
Main configuration function (now async as of v0.2.0):
import envguard from '@envguard/node';
// ES Modules with top-level await
const result = await envguard.config({
environment: 'production',
validate: true,
});
console.log(result.count); // Number of secrets loaded
// Or with IIFE in CommonJS
(async () => {
const result = await envguard.config();
console.log(result.count);
})();Breaking Change (v0.2.0): config() is now async. Use await or .then().
async load(options?: LoadOptions): Promise<LoadResult>
Asynchronous secret loading (alias for config):
import { load } from '@envguard/node';
const result = await load({
environment: 'production', // Environment to load
projectRoot: process.cwd(), // Project directory
packageName: 'my-app', // Override package name
debug: false, // Debug logging
override: false, // Override existing env vars
validate: true, // Validate required secrets
templatePath: '.env.template', // Template file path
});LoadResult:
interface LoadResult {
success: boolean; // Whether loading succeeded
loaded: Record<string, string>; // Loaded secrets
errors: ValidationError[]; // Validation errors
count: number; // Number of secrets loaded
}async populate(options?: PopulateOptions): Promise<Record<string, string>>
Get secrets without injecting into process.env:
import { populate } from '@envguard/node';
// Get secrets as object
const secrets = await populate({
environment: 'production',
});
// Use directly without polluting process.env
const apiClient = new APIClient({
apiKey: secrets.API_KEY,
baseURL: secrets.API_URL,
});reset(options?: ResetOptions): void
Reset EnvGuard state:
import { reset } from '@envguard/node';
// Clear internal state
reset();
// Clear state AND remove from process.env
reset({ cleanEnv: true });detectEnvironment(): string
Get current environment:
import { detectEnvironment } from '@envguard/node';
const env = detectEnvironment();
// Returns: ENVGUARD_ENV → NODE_ENV → 'development'Type Definitions
import type {
LoadOptions,
LoadResult,
PopulateOptions,
ResetOptions,
ValidationError,
} from '@envguard/node';Migration from dotenv
Step-by-Step Guide
1. Install EnvGuard:
npm install @envguard/node
npm uninstall dotenv2. Initialize:
npx envguard init3. Migrate your secrets:
Option A: Manual migration
# For each secret in .env
npx envguard set SECRET_NAME secret_valueOption B: Automated script
# Read from .env and store in keychain
cat .env | while IFS='=' read -r key value; do
[ -n "$key" ] && [ -n "$value" ] && \
npx envguard set "$key" "$value"
done4. Update your code:
// Before
- require('dotenv/config');
+ require('@envguard/node/config');
// Or programmatically (v0.2.0+: now async)
- require('dotenv').config();
+ (async () => { await require('@envguard/node').config(); })();5. Clean up:
# Delete .env file
rm .env
# Remove from git (if committed)
git rm --cached .env
# Add to .gitignore
echo ".envguard/" >> .gitignoreAPI Compatibility
| dotenv | @envguard/node | Status |
| -------------------------- | ----------------------------------- | ------------------------------- |
| dotenv.config() | await envguard.config() (v0.2.0+) | ⚠️ Now async (breaking change) |
| dotenv.parse(src) | envguard.parse(src) | ⚠️ Returns empty (logs warning) |
| - | await envguard.load() | 🆕 New async API |
| - | await envguard.populate() | 🆕 Non-invasive loading |
| - | envguard.reset() | 🆕 State management |
| require('dotenv/config') | require('@envguard/node/config') | ✅ Auto-load still works |
Environment Management
Environment Priority
ENVGUARD_ENV > NODE_ENV > 'development'Multi-Environment Setup
# Development secrets (default environment)
npx envguard set API_KEY dev_key_123
# Staging secrets
npx envguard set API_KEY staging_key_456 --env staging
# Production secrets
npx envguard set API_KEY prod_key_789 --env productionLoad Specific Environment
// Explicit environment
await load({ environment: 'production' });
// Or set via environment variable
process.env.ENVGUARD_ENV = 'staging';
await load(); // Uses 'staging'
// Or via NODE_ENV
process.env.NODE_ENV = 'production';
await load(); // Uses 'production'Environment Detection
import {
detectEnvironment,
isProduction,
isDevelopment,
isTest,
} from '@envguard/node';
if (isProduction()) {
// Strict validation in production
await load({ validate: true });
} else {
// Relaxed in development
await load({ validate: false, debug: true });
}Testing
Testing Utilities
EnvGuard provides testing utilities for easy mocking:
import { createMockKeychain } from '@envguard/node/testing';
import { load } from '@envguard/node';
describe('App Tests', () => {
it('should load secrets', async () => {
// Create mock keychain with test data
const mockKeychain = createMockKeychain({
API_KEY: 'test_key',
DATABASE_URL: 'test_db',
});
// Load with mock keychain
const result = await load({
keychain: mockKeychain,
validate: false,
});
expect(result.success).toBe(true);
expect(result.count).toBe(2);
});
});Helper Functions
import { withEnvVars, withCleanEnv, withReset } from '@envguard/node/testing';
// Run with temporary env vars
await withEnvVars({ NODE_ENV: 'test' }, async () => {
// Your test code
});
// Run with clean environment
await withCleanEnv(async () => {
// No env vars set
});
// Auto-reset after test
await withReset(async () => {
await load();
// Automatically reset after
});MockKeychain API
import { MockKeychain } from '@envguard/node/testing';
const keychain = new MockKeychain();
// CRUD operations
await keychain.set('API_KEY', 'value');
await keychain.get('API_KEY'); // 'value'
await keychain.delete('API_KEY');
await keychain.list(); // []
// Batch operations
keychain.setAll(
{
API_KEY: 'key1',
DB_URL: 'url1',
},
'production'
);
keychain.getAll(); // Get all secretsDocumentation
Core Guides
- Best Practices - Recommended patterns and security practices
- Contributing Guide - How to contribute to the project
- CI/CD Guide - Continuous Integration and Deployment setup
Additional Resources
- EnvGuard CLI - CLI tool for managing secrets
- Architecture - How EnvGuard works
- Security - Security model and considerations
Examples
Express.js
// Load secrets before Express
require('@envguard/node/config');
const express = require('express');
const app = express();
// Use secrets
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});Next.js
// next.config.js
require('@envguard/node/config');
module.exports = {
env: {
API_URL: process.env.API_URL,
// Secrets available during build
},
};NestJS
// src/main.ts
import '@envguard/node/config';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT || 3000);
}
bootstrap();TypeScript + ESM
// src/index.ts
import { load } from '@envguard/node';
async function main() {
// Load secrets
await load({
validate: true,
debug: process.env.DEBUG === 'true',
});
// Import app after secrets loaded
const { startServer } = await import('./server.js');
await startServer();
}
main().catch(console.error);Docker
FROM node:18-alpine
WORKDIR /app
# Install EnvGuard CLI globally
RUN npm install -g @envguard/cli
# Copy app
COPY . .
RUN npm install
# Secrets injected at runtime from host keychain
CMD ["node", "--require", "@envguard/node/register", "index.js"]FAQ
How is this different from dotenv?
| Feature | dotenv | @envguard/node |
| ------------ | ----------------------- | --------------------------- |
| Storage | .env files | OS keychain |
| Security | Files can be committed | Never in repository |
| Multi-env | Multiple .env.* files | Single keychain, namespaced |
| Git-safe | ⚠️ Easy to leak | ✅ Never committed |
| Team sharing | Manual file sharing | Each dev sets own secrets |
Is it secure?
Yes! Secrets are stored in:
- macOS: Keychain Access (encrypted)
- Windows: Credential Manager (encrypted)
- Linux: Secret Service/libsecret (encrypted)
Secrets never touch disk in plaintext.
Can I use this with Docker?
Yes, but secrets must be available in the container's keychain. Options:
- Development: Mount host keychain
- Production: Inject secrets at runtime via env vars or secrets manager
- CI/CD: Use secret management tools
Does this work with environment variables?
Yes! process.env variables take precedence (unless override: true).
// Shell env var
export API_KEY=shell_value
// EnvGuard secret
envguard set API_KEY keychain_value
// Load
await load({ override: false });
console.log(process.env.API_KEY); // 'shell_value' (env var wins)
await load({ override: true });
console.log(process.env.API_KEY); // 'keychain_value' (keychain wins)Can I use this in tests?
Yes! Use MockKeychain:
import { load } from '@envguard/node';
import { createMockKeychain } from '@envguard/node/testing';
test('app loads secrets', async () => {
const result = await load({
keychain: createMockKeychain({ API_KEY: 'test' }),
});
expect(result.success).toBe(true);
});What about CI/CD?
In CI/CD, use environment variables or secret management:
# GitHub Actions
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}Or use a mock keychain in tests.
How do I migrate from dotenv?
See Migration Guide above. TL;DR:
npm install @envguard/nodeenvguard init- Migrate secrets:
envguard set KEY value - Change:
require('dotenv')→require('@envguard/node') - Delete
.envfile
Performance impact?
Minimal! Keychain access is fast:
- Startup time: <100ms for 50 secrets
- Memory: <1MB additional
- No runtime overhead after initial load
Contributing
We welcome contributions! See CONTRIBUTING.md for:
- Development setup
- Coding standards
- Testing guidelines
- PR process
License
Related Packages
@envguard/cli- Command-line interface for managing secrets@envguard/core- Core library (internal)
Support
Made with ❤️ by Aman Nirala
