Type and parse your environment variables with Zod
If you ever worked with environment variables in a TypeScript project, you probably know that it can be a bit of a pain to handle them.
Published on 2023-03-19
If you ever worked with environment variables in a TypeScript project you probably know that it can be a little bit annoying to handle them.
For example, how many times you had to write something like this?
const PORT = process.env.PORT as string
In fact every time you access an environment variable you have to cast it to the correct type, else the Typescript language server will output an error. The reason why this happens is that environment variables are inferred by Typescript as a union between string
and undefined
.
And that actually makes sense because Typescript doesn't know if that variable exists in your .env file.
Casting env variables as string
everywhere in your codebase is not only error-prone but it also makes your code harder to read and maintain, especially when having lot of environment variables.
But this is not the only reason why environment variables can be troublesome, you also have to make sure that they are set up correctly. If you forget to set an environment variable on your .env
file your application will crash. It's really important to validate all of your environment variables before using them.
This is where Zod comes in. Zod is a TypeScript-first schema declaration and validation library. Among other things, it allows you to define types for your env variables and parse them from the environment in a few steps. Let's see how it works.
Create your ENV schema
You can start by installing Zod with this command:
npm install zod
and creating a new file called env.ts
inside the root of your project. This file will essentially contain the schema for your environment variables.
Insdie env.ts
, you can define your schema like this:
import { z } from 'zod'
const envClientSchema = zod.object({
APP_URL: zod.string().url(),
NEXT_PUBLIC_SANITY_PROJECT_ID: zod.string(),
NEXT_PUBLIC_SANITY_DATASET: zod.string(),
NEXT_PUBLIC_SANITY_API_VERSION: zod.string(),
})
Here zod.string()
means that the variable type needs to be a string. By using zod.string().url()
you can be a little bit more specific and say that APP_URL
should not only be a string, but it should also have a URL structure. Keep in mind that even if you use zod.string().url()
the variable will still be inferred as string
by Typescript.
You can differentiate client and server environment variables by creating two different schemas. In this case, we are defining envClientSchema
for client, if you need you can also add another schema called envServerSchema
for server.
Once you've done that, you need to parse the env variables by using Zod parse
method:
const parsedEnvClientSchema = envClientSchema.parse({
APP_URL: process.env.APP_URL,
NEXT_PUBLIC_SANITY_PROJECT_ID: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
NEXT_PUBLIC_SANITY_DATASET: process.env.NEXT_PUBLIC_SANITY_DATASET,
NEXT_PUBLIC_SANITY_API_VERSION: process.env.NEXT_PUBLIC_SANITY_API_VERSION,
})
This will throw an error if any of the environment variables are missing in your .env
file or if they don't match the Zod type assigned in the schema.
Once you've parsed the environment variables you can export your schema:
export const ENV = parsedEnvClientSchema
Finally you can use your environment variables inside the application like this:
import { ENV } from '@/env'
const url = ENV.APP_URL
A couple of things to note:
- now you get autocomplete suggestions when using the
ENV
object - type of
url
is correctly inferred asstring
Conclusions
Typing and parsing env variables with Zod can be really useful and it's quite easy, but that's only one reason why Zod is an awesome library. There are a lot more cool things you can do with it, so make sure to check this blog often because I'm going to write about it more in the future.