Most teams wire up the OpenAI Node.js SDK in a hurry and paste API keys directly into source files. Proper openai node.js sdk authentication env vars configuration keeps secrets out of version control, simplifies rotation, and lets you run the same code against staging and production. This guide lays out a hardened, ordered path from project init to key rotation, with the pitfalls I see in production reviews.
Why env vars beat hardcoded keys
Hardcoding sk-... strings in a repo is how credentials leak. Even private repos get cloned to laptops, bundled into images, and scanned by bots. Using openai node.js sdk authentication env vars moves the secret to the runtime environment, where access controls and secret managers can govern it.
Environment variables also let you swap credentials per deployment without rebuilding artifacts. A CI job can inject a test key; production uses a vault-backed value. The SDK itself does not care where the string came from.
Project setup and dependencies
Start with a clean Node project. Use Node 18+ for native fetch and ESM support.
Installing the SDK
npm init -y
npm install openai
npm install dotenv
The openai package is the official SDK. dotenv loads a local .env file into process.env during development.
Creating a .env file
Never put real secrets in a committed file. Create .env locally and add it to .gitignore.
echo "OPENAI_API_KEY=sk-test-12345" > .env
echo ".env" >> .gitignore
For production, inject the variable through your platform (Vercel, Fly, Kubernetes Secrets) rather than shipping the file.
Loading variables the right way
In development, dotenv is convenient. In production, the OS or orchestrator already provides process.env. Calling dotenv.config() there is harmless but redundant.
dotenv vs process.env in production
// at the top of your entry file
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
const apiKey = process.env.OPENAI_API_KEY;
This pattern avoids masking real env vars with a stale local file. If the platform sets the variable, dotenv will not overwrite it by default, but the guard makes intent explicit.
Typing your config
If you use TypeScript, declare the expected shape:
declare global {
namespace NodeJS {
interface ProcessEnv {
OPENAI_API_KEY: string;
OPENAI_BASE_URL?: string;
}
}
}
This catches missing vars at compile time in strict mode, though runtime checks are still required.
Initializing the client with env vars
The SDK reads apiKey from the constructor. Do not read it from a JSON config or a remote fetch at call time; pass it once at boot.
Basic OpenAI client
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Ping' }],
});
If apiKey is undefined, the SDK throws at request time with a vague error. We will fix that with fail-fast validation below.
Pointing at an OpenAI-compatible gateway
Many teams proxy through a gateway to access multiple model providers. If you route through a gateway such as n4n.ai, set OPENAI_BASE_URL to its endpoint and reuse the same key variable; the gateway honors the standard auth header and adds fallback across providers.
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL ?? 'https://api.openai.com/v1',
});
The baseURL override is the only change needed for any OpenAI-compatible endpoint. Keep the variable name OPENAI_API_KEY to avoid confusing tooling.
Handling missing or malformed vars
A silent undefined key produces late, confusing failures. Validate at process start.
Fail fast at boot
function loadConfig() {
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey || !apiKey.startsWith('sk-')) {
throw new Error('OPENAI_API_KEY is missing or malformed');
}
return { apiKey, baseURL: process.env.OPENAI_BASE_URL };
}
const config = loadConfig();
const openai = new OpenAI(config);
This crashes the service before it accepts traffic, which is preferable to 500s on every request.
Validation with zod
For larger apps, use a schema:
import { z } from 'zod';
const EnvSchema = z.object({
OPENAI_API_KEY: z.string().startsWith('sk-'),
OPENAI_BASE_URL: z.string().url().optional(),
});
const parsed = EnvSchema.parse(process.env);
Zod gives clear error messages and centralizes env contracts.
Rotating keys without downtime
OpenAI supports multiple active keys. If you use a secret manager, rotate by issuing a new key, updating the env var, and restarting or sending a SIGHUP to reload.
For zero-downtime in long-running processes, build a client factory that reads the key from a getter:
let currentKey = process.env.OPENAI_API_KEY!;
function rotateKey(newKey: string) { currentKey = newKey; }
const openai = new OpenAI({
apiKey: () => currentKey,
});
The SDK accepts a function for apiKey, evaluating it per request. This avoids process restarts during rotation.
Tradeoff: a function getter slightly increases per-call overhead and can complicate testing. For most services, env reload + restart is simpler.
Using multiple keys for rate limits
If you hit provider rate limits, round-robin across several keys from a comma-separated env var:
const keys = (process.env.OPENAI_API_KEYS ?? '').split(',').filter(Boolean);
let i = 0;
const openai = new OpenAI({
apiKey: () => keys[i++ % keys.length],
});
This is a client-side workaround. A gateway such as n4n.ai performs automatic fallback when a provider is rate-limited or degraded, removing the operational burden of key pools.
Testing with mocked environment
The same openai node.js sdk authentication env vars approach works in tests with fake values. Never import the real .env in test runs.
Isolating test config
// test/setup.ts
process.env.OPENAI_API_KEY = 'sk-test-fake';
process.env.OPENAI_BASE_URL = 'http://localhost:4010/v1'; // mock server
Use a mock like msw or openai-mock to stub responses. This keeps tests hermetic and avoids accidental billing.
CI secrets
In GitHub Actions, set the var under env: from a repo secret. Never echo it.
jobs:
test:
steps:
- run: npm test
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
Tradeoff: storing a real key in CI lets integration tests run, but increases blast radius if the runner is compromised. Prefer recorded fixtures.
Common pitfalls
Accidental logging of keys
Never log process.env wholesale. Structured loggers may serialize the whole object. Redact:
console.log('config', { baseURL: config.baseURL, keySet: !!config.apiKey });
Committing .env to git
Use git secrets or a pre-commit hook. Once a key hits history, rotate it—removing the file later does not purge the leak.
Mixing server and client env
The OpenAI SDK runs on the server. Exposing OPENAI_API_KEY to browser bundles via NEXT_PUBLIC_ or similar prefixes is a critical mistake. Client-side calls must go through your own backend proxy.
Base URL overrides and proxy pitfalls
If you set baseURL without a trailing /v1, the SDK appends paths incorrectly. Always include the full base including /v1 for OpenAI, or follow the gateway’s docs. Misconfigured proxies also strip the Authorization header; test with a curl.
curl -H "Authorization: Bearer $OPENAI_API_KEY" $OPENAI_BASE_URL/models
Case sensitivity and shell quoting
Env var names are case-sensitive on Linux. openai_api_key will not be read. Quote values containing special characters in shell exports to avoid truncation.
Tradeoffs of env var auth
Env vars are simple but not encrypted at rest. On a shared host, any process can read them. For high-security workloads, use a secret manager that mounts files or provides short-lived tokens. Env vars also cannot enforce per-tenant isolation; if you need that, proxy the SDK calls through a service that swaps keys contextually.
Another tradeoff: env vars are process-global. If you serve multiple customers with different keys, you must instantiate multiple clients or use the function getter pattern. That is fine at small scale but adds memory overhead.
Quick reference checklist
- Install
openaianddotenv; gitignore.env. - Load env early; guard dotenv in non-production.
- Validate
OPENAI_API_KEYat boot with a prefix check or zod. - Pass
apiKeyand optionalbaseURLfrom env to the client constructor. - Use a function getter only if you need hot rotation.
- Redact keys from logs; never expose to client code.
- Rotate by updating the env and restarting, or via getter for zero-downtime.
Following this path makes openai node.js sdk authentication env vars a non-event in your deploy pipeline rather than a recurring incident.