Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Environment Variables - Zagora
Skip to content

Environment Variables

Type-safe env var validation

Zagora lets you validate environment variables with the same schema system used for inputs and outputs.

Basic Usage

Use .env() to define and validate environment variables:

import { z } from 'zod';
import { zagora } from 'zagora';
 
const apiCall = zagora()
  .env(z.object({
    API_KEY: z.string().min(1),
    API_URL: z.string().url(),
    TIMEOUT: z.coerce.number().default(5000)
  }))
  .input(z.string())
  .handler(({ env }, endpoint) => {
    // env.API_KEY: string
    // env.API_URL: string
    // env.TIMEOUT: number
    return fetch(`${env.API_URL}${endpoint}`, {
      headers: { 'Authorization': `Bearer ${env.API_KEY}` },
      signal: AbortSignal.timeout(env.TIMEOUT)
    });
  })
  .callable({ env: process.env as any });

Providing Env at Runtime

Pass environment variables via .callable():

const proc = zagora()
  .env(z.object({
    DATABASE_URL: z.string()
  }))
  .handler(({ env }) => connectToDatabase(env.DATABASE_URL))
  .callable({ env: process.env as any });

Coercion

Use .coerce to convert string env vars to proper types:

.env(z.object({
  PORT: z.coerce.number(),           // "3000" -> 3000
  DEBUG: z.coerce.boolean(),         // "true" -> true
  TIMEOUT: z.coerce.number().default(5000)
}))

Default Values

Defaults work as expected:

.env(z.object({
  NODE_ENV: z.enum(['development', 'production']).default('development'),
  LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
  MAX_RETRIES: z.coerce.number().default(3)
}))

Optional Env Vars

Use .optional() for truly optional variables:

.env(z.object({
  API_KEY: z.string(),                    // Required
  SECONDARY_KEY: z.string().optional()    // Optional
}))
.handler(({ env }) => {
  // env.API_KEY: string
  // env.SECONDARY_KEY: string | undefined
})

Env Validation Errors

Invalid env vars result in a VALIDATION_ERROR:

const proc = zagora()
  .env(z.object({
    PORT: z.coerce.number().min(1).max(65535)
  }))
  .handler(({ env }) => env.PORT)
  .callable({ env: { PORT: 'invalid' } });
 
const result = proc();
// result.ok === false
// result.error.kind === 'VALIDATION_ERROR'

With autoCallable

When using autoCallable, provide env vars as the second argument to .env():

const proc = zagora({ autoCallable: true })
  .env(
    z.object({
      API_KEY: z.string()
    }),
    process.env as any  // Second argument: runtime env
  )
  .input(z.string())
  .handler(({ env }, input) => {
    return fetch(`/api/${input}`, {
      headers: { 'X-API-Key': env.API_KEY }
    });
  });
 
// Direct call - no .callable() needed
proc('users');

Without disableOptions

Pattern: Config Procedure

Create a config-loading procedure:

const getConfig = zagora()
  .env(z.object({
    DATABASE_URL: z.string(),
    REDIS_URL: z.string().optional(),
    JWT_SECRET: z.string().min(32),
    PORT: z.coerce.number().default(3000),
    NODE_ENV: z.enum(['development', 'staging', 'production']).default('development')
  }))
  .handler(({ env }) => ({
    database: { url: env.DATABASE_URL },
    redis: env.REDIS_URL ? { url: env.REDIS_URL } : null,
    auth: { secret: env.JWT_SECRET },
    server: { port: env.PORT },
    env: env.NODE_ENV
  }))
  .callable({ env: process.env as any });
 
const config = getConfig();
if (config.ok) {
  startServer(config.data);
}

Pattern: Per-Environment Config

const createApiClient = zagora()
  .env(z.object({
    API_URL: z.string().url(),
    API_KEY: z.string()
  }))
  .input(z.string())
  .handler(({ env }, endpoint) => {
    return fetch(`${env.API_URL}${endpoint}`, {
      headers: { 'Authorization': `Bearer ${env.API_KEY}` }
    });
  });
 
// Production
const prodClient = createApiClient.callable({
  env: { API_URL: 'https://api.production.com', API_KEY: prodKey }
});
 
// Staging
const stagingClient = createApiClient.callable({
  env: { API_URL: 'https://api.staging.com', API_KEY: stagingKey }
});

Limitations

  • Async schema validation for env vars is not supported
  • Env vars are validated once at .callable() time, not per-call

Next Steps