Understanding Environment Variables in Node.js
In Node.js (and frameworks like NestJS), environment variables, typically managed through .env files and accessed using process.env, are always strings. This subtle fact can introduce unexpected bugs, especially when you're expecting a boolean (true or false) but get a string ("true" or "false").
Real-World Example in a Node.js/NestJS Project
Suppose you have a .env file like this:
DB_SYNCHRONIZE=false
DB_LOGGING=true
And in your configuration code:
database: {
synchronize: process.env.DB_SYNCHRONIZE,
logging: process.env.DB_LOGGING,
}
You expect synchronize to be false and logging to be true.
But in reality, both will be truthy because they are non-empty strings!
The Bug in Action
Why is this a problem?
console.log(process.env.DB_SYNCHRONIZE); // "false" (string)
if (process.env.DB_SYNCHRONIZE) {
console.log("This runs!"); // This will run, even though you set it to "false"
}
This is because in JavaScript:
- "false" (string) is truthy
- Only "", 0, null, undefined, and false (boolean) are falsy
Boolean("false"); // true
Boolean("true"); // true
Boolean("") // false
So, if you pass process.env.DB_SYNCHRONIZE directly to a library expecting a boolean, it will act as true!
So "false" (a non-empty string) is truthy, and the if block runs, even though logically you set it to false!
This behavior can lead to critical misconfigurations in production environments. For example, enabling logging, enabling DB synchronization (which drops and recreates tables in some ORMs), or accidentally exposing debug features.
Always convert environment variable strings to booleans explicitly.
The Correct Approach: Explicit Type Conversion
Boolean Conversion Pattern
Always parse environment variables to their expected types before use.
const synchronize = process.env.DB_SYNCHRONIZE === 'true';
const logging = process.env.DB_LOGGING === 'true';
This way:
'true' === 'true' → true
'false' === 'true' → false
You now get predictable, safe boolean values.
Optional Utility Function
For larger projects, it's wise to create a helper:
function toBool(value: string | undefined): boolean {
return value?.toLowerCase() === 'true';
}
const synchronize = toBool(process.env.DB_SYNCHRONIZE);
const logging = toBool(process.env.DB_LOGGING);
Pro Tip: Use a Config Validation Library
Frameworks like NestJS recommend using a validation layer (e.g., with Joi or zod) to validate and transform config values at startup.
Example using Joi:
const configSchema = Joi.object({
DB_SYNCHRONIZE: Joi.boolean().default(false),
DB_LOGGING: Joi.boolean().default(false),
});
Real-Life Pitfall: When "false" Isn’t Really False
This isn’t just theory, I ran into this issue myself while working on a NestJS project using TypeORM.
I had this in my .env:
DB_SYNCHRONIZE=false
And my config looked like:
synchronize: process.env.DB_SYNCHRONIZE,
I assumed this would disable TypeORM’s automatic schema synchronization, since I planned to use migrations instead.
But when I started the app, the tables were still being created. Then, when I tried to generate a migration:
typeorm migration:generate -n AddNewFields
I kept seeing:
No changes in database schema were found - cannot generate a migration.
It was confusing, until I logged the value of process.env.DB_SYNCHRONIZE and saw:
console.log(typeof process.env.DB_SYNCHRONIZE); // string
console.log(process.env.DB_SYNCHRONIZE); // "false"
That string "false" is truthy, so synchronize: "false" is effectively treated as true in JavaScript. TypeORM was happily syncing the schema on every run, and by the time I tried to run the migration generator, there were no changes left to detect.
Final Thoughts
Misinterpreting environment variables is one of those bugs that can sneak into production unnoticed until something critical misbehaves. In systems where flags control behavior like syncing databases, enabling debug logs, or toggling features, treating "false" as a boolean without conversion can be dangerous.
Always treat process.env values as untrusted strings and explicitly cast them to the types your logic expects. Doing this upfront helps you avoid fragile assumptions and keeps your config layer predictable.