Cover image for blog post: "How the environment variables are configured in a production grade system ?"
Back to blog posts

How the environment variables are configured in a production grade system ?

In this blog, we explore how to properly configure and validate environment variables in a Node.js project. Renish shares insights on the importance of environment variables, which help abstract sensitive information and prevent hardcoding.

Published onJanuary 29, 20223 minutes read

Table of Contents

Hi, explorers & tech enthusiasts. I am Renish, a software engineer based in India.

Today, while setting up my project structure, I wondered, how can I ensure that my environment variables are type-safe and validated before the project starts? After some exploration, I found two key approaches:

  1. Loading environment variables using functions and validating them with libraries like JOI, AJV, or Zod.
  2. Using cloud platforms to centrally manage environment variables for security and consistency across the team.

1) Validating Environment Variables

In one of my previous projects, we had process.env.VAR_NAME scattered throughout the codebase. Sure, you might think, just use Ctrl+F, copy, paste, and fix it! But trust me, the codebase was massive, and the real headache came when DevOps announced, From now on, we’ll load all environment variables from a configuration file.

Let the ride begin! Here’s how I tackled the challenge.

What Are Environment Variables?

Environment variables are external, configurable data used in our application that are loaded at runtime. They help keep sensitive information (like API keys, database URLs, and authentication secrets) abstracted from the codebase, reducing the risk of hardcoding these values directly into the source code.

How Do We Configure Environment Variables Properly?

A common issue many developers face is the risk of environment variables being undefined or incorrectly typed. This can lead to errors that might not be caught until runtime. To avoid this, we need two key practices:

  1. Validation: Ensuring that all necessary environment variables are present and are in the correct format (e.g., string, number, boolean).
  2. Type Safety: Validating and casting values into the right types to avoid bugs caused by unexpected variable types.

Let’s first create a config.js file that will load all our variables from the .env file.

  1. Load Environment Variables with dotenv By calling dotenv.config(), your environment variables defined in the .env file become available globally in your application. This is important because it allows your app to access variables such as database URLs, API keys, and configuration settings.
// config.ts
import dotenv from 'dotenv';
dotenv.config();
  1. Validating Variables with zod To ensure the environment variables are set correctly and contain valid values, we use zod, a schema validation library. This is where the magic of type safety and reliability comes into play.
// config.ts
 
import dotenv from 'dotenv';
import { z } from 'zod';
 
dotenv.config();
 
const envSchema = z.object({
  API_SECRET: z.string(),
 
  LOG_LEVEL: z.enum(['info', 'debug', 'error']),
 
  DATABASE_URL: z
    .string()
    .refine((url) => url.startsWith('mongodb'), 'Invalid DATABASE_URL format'),
 
  PORT: z.string().refine(
    (port) => parseInt(port) > 0 && parseInt(port) < 65536,
 
    'Invalid port number',
  ),
 
  NODE_ENV: z.string().refine(
    (env) =>
      env.toLowerCase().match('development') ||
      env.toLowerCase().match('production'),
 
    'Invalid environment',
  ),
});
 
type Env = z.infer<typeof envSchema>;
 
export const env: Env = envSchema.parse(process.env);
  1. Inferring the Environment Schema We use zod’s infer utility to infer the TypeScript type of the environment variables based on our schema. This gives us type safety throughout the application whenever we access env.
type Env = z.infer<typeof envSchema>;

Finally, we parse the environment variables using zod. If any validation rules are violated, an error will be thrown, preventing the application from running with invalid configurations.

Key Takeaways

  1. Declare all environment variables in one place to centralize configuration.
  2. Use validation libraries like Zod, Joi, or AJV to ensure all variables are present and correctly formatted.
  3. Typecast the variables (like converting a port number from a string to an integer) to avoid type errors later.
  4. This approach not only makes your environment variables type-safe but also prevents the app from starting with missing or invalid configurations—an essential step for any production-grade system.

Conclusion

This approach ensures that your Node.js application can rely on properly formatted and validated environment variables. It catches configuration errors early, improving stability and reducing runtime issues caused by invalid environment settings. With libraries like dotenv and zod, you can combine flexibility and strong validation to safely manage your app’s configuration.

In the next section, I’ll dive deeper into how cloud platforms can help manage environment variables securely across teams. Stay tuned!