To validate environment variables using zod, you can achieve two things:
- If any required environment variables are missing, crash the app on startup.
- Add type definitions for environment variables so that we can get auto-completion and type checking in our IDE.
Let's dive into the code:
const envSchema = z.object({
SESSION_SECRET: z.string().min(1),
DOMAIN_NAME: z.string().min(1),
})
declare global {
namespace NodeJS {
interface ProcessEnv extends TypeOf<typeof envSchema> {}
}
}
envSchema.parse(process.env)
For Remix, you can do it like this:
// env.server.ts
import { TypeOf, z } from 'zod'
const envSchema = z.object({
SESSION_SECRET: z.string().min(1),
DOMAIN_NAME: z.string().min(1),
})
declare global {
namespace NodeJS {
interface ProcessEnv extends TypeOf<typeof envSchema> {}
}
}
try {
envSchema.parse(process.env)
} catch (err) {
if (err instanceof z.ZodError) {
console.error(err.flatten())
const { fieldErrors } = err.flatten()
const errorMessage = Object.entries(fieldErrors)
.map(([field, errors]) => (errors ? `${field}: ${errors.join(', ')}` : field))
.join('\n ')
throw new Error(`Missing environment variables:\n ${errorMessage}`)
}
}
Then import it in entry.server.ts
:
import '~/env.server'
Bonus#
Alternatively, you can directly use t3 env, the principle is the same.
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
OPEN_AI_API_KEY: z.string().min(1),
},
/**
* The prefix that client-side variables must have. This is enforced both at
* a type-level and at runtime.
*/
clientPrefix: "PUBLIC_",
client: {
PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
},
/**
* What object holds the environment variables at runtime. This is usually
* `process.env` or `import.meta.env`.
*/
runtimeEnv: process.env,
/**
* By default, this library will feed the environment variables directly to
* the Zod validator.
*
* This means that if you have an empty string for a value that is supposed
* to be a number (e.g. `PORT=` in a ".env" file), Zod will incorrectly flag
* it as a type mismatch violation. Additionally, if you have an empty string
* for a value that is supposed to be a string with a default value (e.g.
* `DOMAIN=` in an ".env" file), the default value will never be applied.
*
* In order to solve these issues, we recommend that all new projects
* explicitly specify this option as true.
*/
emptyStringAsUndefined: true,
});