Environment variables are validated at startup using a Zod schema. If a required variable is missing, the server fails fast with a clear error message instead of crashing later with a cryptic undefined.
Defined in src/config/env/env.config.ts:
import { z } from "zod";
export const envSchema = z.object({
FRONTEND_URL: z.string(),
DATABASE_URL: z.string(),
VALKEY_URL: z.string(),
WORKOS_COOKIE_PASSWORD: z.string(),
SENTRY_DSN: z.string(),
RESEND_API_KEY: z.string(),
STRIPE_SECRET_KEY: z.string(),
STRIPE_WEBHOOK_SECRET: z.string(),
// ... more variables
});
export type Env = z.infer<typeof envSchema>;
The validateEnv() function runs at server startup:
export const validateEnv = () => {
try {
envSchema.parse(process.env);
} catch (err) {
if (err instanceof z.ZodError) {
const errorMessage = z.prettifyError(err);
throw new Error(`Missing environment variables:\n ${errorMessage}`, { cause: err });
}
}
};
If any variable is missing or has the wrong type, you get a list of exactly what's wrong before the server tries to use them.
src/config/env/env.config.ts.env.example so other developers know about it.envz.string().optional() if the variable isn't required in all environmentsFrontend variables are prefixed with VITE_ and accessed via import.meta.env:
const apiUrl = import.meta.env.VITE_API_BASE_URL;
These are baked into the bundle at build time. Don't put secrets here.