Ensuring Environment Variable Integrity with Zod in TypeScript

Marcos Schead - May 16 - - Dev Community

Managing environment variables effectively is crucial in any application, whether it’s a frontend or backend. Missing or misconfigured environment variables can lead to hard-to-debug runtime errors. Fortunately, the Zod library offers a simple yet powerful solution for schema validation, which can be leveraged to validate environment variables upfront. In this blog post, we'll explore how to use Zod to ensure all necessary environment variables are set correctly, preventing unexpected runtime issues.

Additionally, we will cover how to manage environment variables using a .env file in a Node.js project with TypeScript.

What is Zod?

Zod is a TypeScript-first schema declaration and validation library. It provides a straightforward way to define schemas and validate data against these schemas. It can be used to validate inputs, responses, and, importantly, environment variables.

Why Validate Environment Variables?

Environment variables are used to configure various aspects of an application, such as database connections, API endpoints, and ports. If these variables are missing or misconfigured, your application might crash or behave unpredictably. Validating these variables at startup ensures that all required configuration is present and correctly formatted, providing an early failure mechanism that saves time and effort in debugging.

Step-by-Step Guide to Using Zod for Environment Variables

Let’s walk through how to set up Zod to validate environment variables in a TypeScript project and how to use a .env file to manage these variables.

Step 1: Initialize Your Node.js Project

First, you need to initialize a new Node.js project if you haven't already. This will create a package.json file to manage your project's dependencies and scripts.

npm init -y
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Dependencies

Next, install Zod and dotenv in your project. You will also need express, @types/express, @types/node, and ts-node for a complete setup.

npm install zod dotenv express
npm install --save-dev @types/express @types/node ts-node typescript
Enter fullscreen mode Exit fullscreen mode

Step 3: Set Up TypeScript Configuration

Ensure your tsconfig.json is properly configured for TypeScript. Here is a basic example:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a .env File

Create a .env file in the root of your project. This file will store your environment variables.

PORT=3000
BASE_URL=http://localhost
Enter fullscreen mode Exit fullscreen mode

Step 5: Configure dotenv in Your Project

Create a new file, env.ts, where you will configure dotenv and define the schema for your environment variables using Zod.

import { z } from 'zod';
import dotenv from 'dotenv';

dotenv.config();

const envSchema = z.object({
  PORT: z
    .string()
    .refine(
      (port) => parseInt(port) > 0 && parseInt(port) < 65536,
      "Invalid port number"
    ),
  BASE_URL: z
    .string()
    .refine(
      (url) => url.startsWith("http") || url.startsWith("https"),
      "Invalid URL format"
    ),
});

type Env = z.infer<typeof envSchema>;

export const ENV: Env = envSchema.parse(process.env);
Enter fullscreen mode Exit fullscreen mode

In this schema, we define that PORT and BASE_URL are required environment variables and should be strings. The PORT must be a valid port number between 1 and 65535, and BASE_URL must start with "http" or "https". The z.infer utility type is used to infer the TypeScript type from the schema.

Step 6: Validate the Environment Variables

By calling envSchema.parse(process.env), Zod will validate the process.env object against the defined schema. If any of the variables are missing or do not match the defined type, Zod will throw an error, causing your application to crash with a clear message about what’s wrong.

Step 7: Integrate the Validation in Your Application

To ensure the validation runs before the rest of your application, import the env.ts file at the very start of your main entry file, usually index.ts.

import { ENV } from './env';

// Rest of your application code
import express from 'express';

const app = express();
const port = Number(ENV.PORT);

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(port, () => {
  console.log(`Server running at ${ENV.BASE_URL}:${port}`);
});
Enter fullscreen mode Exit fullscreen mode

Step 8: Running Your TypeScript Project

Make sure to run your project using ts-node. You can add a script to your package.json to make this easier:

{
  "scripts": {
    "start": "ts-node src/index.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 9: Demonstrate the Validation

To see Zod's validation in action, intentionally set an incorrect value in the .env file. For example, set PORT to an invalid number and BASE_URL to a string that doesn't start with "http" or "https".

Modify your .env file as follows:

PORT=70000
BASE_URL=localhost
Enter fullscreen mode Exit fullscreen mode

Run your application using the start script:

npm start
Enter fullscreen mode Exit fullscreen mode

You should see an error message indicating the invalid values:

Invalid environment variables: [
  {
    "path": ["PORT"],
    "message": "Invalid port number"
  },
  {
    "path": ["BASE_URL"],
    "message": "Invalid URL format"
  }
]
Enter fullscreen mode Exit fullscreen mode

Conclusion

Using Zod to validate your environment variables is a straightforward and effective way to prevent configuration-related issues. By defining a schema and validating your environment variables at startup, you can ensure that your application fails early and clearly, saving time and avoiding potential runtime errors. This approach can be applied to both frontend and backend environments, making it a versatile tool in your development toolkit.

Additionally, leveraging the dotenv package allows you to manage your environment variables in a simple and organized manner, especially useful in a Node.js project with TypeScript.

Give it a try in your next TypeScript project and experience the peace of mind that comes with knowing your environment is correctly configured from the get-go. Happy coding!

. . . . . .