Skip to content

Fully typed environemnt variables with zod

Posted on:January 25, 2023

We all got used to the great DX of typescript, but when it comes to environment variables, we are back to the old days of javascript. We have to check if the variable is defined, if it is a string, if it is a number, etc. This is where zod comes in. Zod is a great library that allows us to define a schema and then use it to validate any input. Let’s see how we can use it. It provides both a runtime and a compile time validation on top of great type inference.

Define a schema

First, we need to define a schema. We can do it by using the z.object function. It takes an object with the keys and the types of the values.

import * as z from "zod";
const schema = z.object({
PORT: z.number(),
NODE_ENV: z.string(),
DATABASE_URL: z.string(),
});

Create type helpers

We can use the infer keyword to create a type helper that will infer the types of the schema.

type ENV = z.infer<typeof schema>;
type EnvVar = keyof ENV;

Create a getter function

Now we get to the fun part. We can create a function that will take a key and return the value of the environment variable. We are going to use the infered type to make sure that the key is a valid key of the schema.

export function getEnvVar<K extends EnvVars, D extends ENV[K]>(key: K): ENV[K] {
return envVars.parse(process.env);
}

Few things to note here:

  1. We are using the parse function of the schema to parse the environment variables. This will throw an error if the environment variables are not valid.
  2. We are using the ENV[K] type to make sure that the return type is the same as the type of the schema.
  3. We are using the EnvVars type to make sure that the key is a valid key of the schema.

Add a default value

We can even turn it up a notch and add a default value. We can use the NonNullable type to make sure that the default value is not null or undefined.

export function getEnvVar<K extends EnvVars, D extends ENV[K]>(
key: K,
defaultValue?: D
): D extends undefined ? ENV[K] | undefined : NonNullable<ENV[K]> {
const envs = envVars.parse(process.env);
const value = envs[key];
if (value) {
return value;
}
if (defaultValue) {
return defaultValue;
}
return undefined as any;
}

Usage

const PORT = getEnvVar("PORT", 3000);
const NODE_ENV = getEnvVar("NODE_ENV", "development");
const DATABASE_URL = getEnvVar("DATABASE_URL");

Conclusion

We can now use the environment variables with the same great DX of typescript. We get the runtime safety and the great DX ergonomics of typescript for our environemt variables.