npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@envguard/node

v0.2.0

Published

EnvGuard Node.js runtime - Drop-in dotenv replacement with OS keychain

Downloads

14

Readme

@envguard/node

Secure, local-first secret management for Node.js - Drop-in replacement for dotenv that stores secrets in OS keychain instead of .env files.

npm version License: MIT TypeScript Node.js

Table of Contents

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/node

2. 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/mydb

3. 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/node

yarn

yarn add @envguard/node

pnpm

pnpm add @envguard/node

Requirements

  • 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.js

Environment 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.js

API 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 dotenv

2. Initialize:

npx envguard init

3. Migrate your secrets:

Option A: Manual migration

# For each secret in .env
npx envguard set SECRET_NAME secret_value

Option 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"
done

4. 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/" >> .gitignore

API 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 production

Load 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 secrets

Documentation

Core Guides

Additional Resources

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:

  1. Development: Mount host keychain
  2. Production: Inject secrets at runtime via env vars or secrets manager
  3. 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:

  1. npm install @envguard/node
  2. envguard init
  3. Migrate secrets: envguard set KEY value
  4. Change: require('dotenv')require('@envguard/node')
  5. Delete .env file

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

MIT © Aman Nirala


Related Packages

Support


Made with ❤️ by Aman Nirala