Configuración de entorno con seguridad de tipos y validación en tiempo de ejecución
Valide las variables de entorno y las entradas externas en tiempo de ejecución mediante bibliotecas de esquemas inferidas en tipos estáticos.
Configuración de entorno con seguridad de tipos y validación en tiempo de ejecución es una lección gratuita de Node.js Backend Development Bootcamp en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Node.js Backend Development Bootcamp, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Node.js Backend Development Bootcamp incluye 4 lecciones en total.
Partes de esta lección aún no han sido traducidas y se muestran en inglés.
Why Validate process.env?
In Node.js, every value on process.env is a string or undefined — the runtime gives you no guarantees.
process.env.PORTmight be"3000","", or missing entirely.- A typo like
DATABSE_URLsilently yieldsundefined. - TypeScript types
process.envasRecord<string, string | undefined>, so it can't catch missing keys.
Reading config ad-hoc throughout your app means crashes surface deep inside request handlers — long after startup. The fix: validate once at boot and fail fast with a clear message.
// Untyped, unsafe access scattered everywhere
const port = process.env.PORT; // string | undefined
const dbUrl = process.env.DATABASE_URL; // string | undefined
console.log(typeof port); // "string" or "undefined"
console.log(Number(process.env.MISSING)); // NaN — silent failureSchema Libraries and Type Inference
A schema library lets you describe the shape and constraints of data once, then validate at runtime AND infer a static TypeScript type from the same definition.
- Popular choices: Zod, Valibot, ArkType, TypeBox.
- One source of truth — the schema — produces both the runtime check and the compile-time type.
- No duplicated
interfacethat can drift out of sync.
We'll use Zod, the most common in the Node.js ecosystem. z.infer<typeof schema> extracts the type the schema validates.
import { z } from "zod";
const UserSchema = z.object({
id: z.number().int(),
email: z.string().email(),
});
// Static type inferred from the runtime schema
type User = z.infer<typeof UserSchema>;
// type User = { id: number; email: string }A First Env Schema
Let's describe the environment our service needs. Because process.env values are always strings, the schema must coerce numeric fields and constrain string fields.
z.coerce.number()turns"3000"into3000.z.enum([...])restricts a value to a fixed set.- Chaining like
.min()/.url()adds runtime constraints.
Define the schema in its own module (e.g. src/env.ts) so it's imported once at startup.
import { z } from "zod";
export const EnvSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
PORT: z.coerce.number().int().positive().default(3000),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
});Parsing at Startup
Call schema.parse(process.env) once, at the top of your entry file. If validation fails, Zod throws a ZodError and the process exits before serving any traffic.
parse()returns a fully typed, validated object.- Coerced and defaulted values are already applied.
- Export the result so the rest of the app imports a typed
envobject instead of touchingprocess.envdirectly.
import { z } from "zod";
import { EnvSchema } from "./env-schema";
export const env = EnvSchema.parse(process.env);
// env.PORT is number, env.NODE_ENV is a narrowed union
const server = createServer();
server.listen(env.PORT, () => {
console.log(`Listening on ${env.PORT} in ${env.NODE_ENV}`);
});safeParse for Friendly Errors
A raw ZodError stack trace is noisy. Use safeParse to get a result object you can format into a readable startup message, then exit deliberately.
safeParsereturns{ success: true, data }or{ success: false, error }— it never throws.error.flatten().fieldErrorsgroups messages per field.- Exit with
process.exit(1)so orchestrators (Docker, PM2, k8s) see a failed boot.
import { z } from "zod";
const EnvSchema = z.object({
PORT: z.coerce.number().int().positive(),
DATABASE_URL: z.string().url(),
});
const parsed = EnvSchema.safeParse(process.env);
if (!parsed.success) {
console.error("Invalid environment variables:");
console.error(parsed.error.flatten().fieldErrors);
process.exit(1);
}
export const env = parsed.data;A Standalone, Runnable Example
Here is a self-contained demonstration of the validate-then-infer pattern using a plain object instead of process.env, so an online judge can run it with no setup.
It shows coercion, a default, an enum, and a friendly error path — the same techniques you'd apply to real environment loading.
import { z } from "zod";
const Schema = z.object({
NODE_ENV: z.enum(["development", "production"]).default("development"),
PORT: z.coerce.number().int().positive(),
});
function loadConfig(raw) {
const result = Schema.safeParse(raw);
if (!result.success) {
throw new Error(JSON.stringify(result.error.flatten().fieldErrors));
}
return result.data;
}
const config = loadConfig({ PORT: "8080" });
console.log(config); // { NODE_ENV: 'development', PORT: 8080 }
console.log(typeof config.PORT); // numberLoading .env Files with ESM
Locally you store config in a .env file. The library dotenv reads it into process.env. With ESM and TypeScript, load it before any module that reads config.
- Modern Node (v20.6+) has a built-in
--env-file=.envflag — no dependency needed. - If you use
dotenv, calldotenv/configat the very top, since ESM imports are hoisted and evaluated first. - Never commit real secrets; commit a
.env.exampledocumenting required keys.
// Option A: built-in (Node 20.6+), no import needed
// $ node --env-file=.env dist/index.js
// Option B: dotenv — must run first
import "dotenv/config";
import { EnvSchema } from "./env-schema";
export const env = EnvSchema.parse(process.env);Reusing the Schema for Request Input
The exact same approach validates untrusted runtime input — request bodies, query params, webhook payloads. Network data is just as untyped as process.env.
- Define a schema per endpoint, then
parsethe incoming JSON. - On failure, respond with
400instead of crashing. - The parsed result is fully typed for the rest of the handler.
import { z } from "zod";
const CreateUserBody = z.object({
email: z.string().email(),
age: z.coerce.number().int().min(0).optional(),
});
function handleCreateUser(rawBody, res) {
const result = CreateUserBody.safeParse(rawBody);
if (!result.success) {
res.status(400).json({ errors: result.error.flatten() });
return;
}
const body = result.data; // { email: string; age?: number }
// ...persist body
}Transforms and Derived Config
Schemas can transform values during parsing, producing config that's ready to use. This keeps conversion logic next to the validation rule.
.transform()maps a validated value into a new shape.- Comma-separated env strings become arrays; flags become booleans.
- The inferred type reflects the transformed output, not the raw input.
import { z } from "zod";
const EnvSchema = z.object({
// "a.com,b.com" -> ["a.com", "b.com"]
CORS_ORIGINS: z.string()
.transform((s) => s.split(",").map((o) => o.trim()))
.pipe(z.array(z.string().url())),
// "true"/"false" string -> boolean
ENABLE_CACHE: z
.enum(["true", "false"])
.transform((v) => v === "true")
.default("false"),
});Cross-Field Rules with refine
Sometimes validity depends on relationships between fields — e.g. in production a secret must be set, but locally a default is fine. Use .refine() or .superRefine() on the object schema.
- The check runs after individual fields parse.
- You attach a custom message and a
pathso the error points at the right field. - This encodes business rules that a flat per-field schema cannot.
import { z } from "zod";
const EnvSchema = z
.object({
NODE_ENV: z.enum(["development", "production"]),
SENTRY_DSN: z.string().url().optional(),
})
.refine(
(e) => e.NODE_ENV !== "production" || !!e.SENTRY_DSN,
{ message: "SENTRY_DSN is required in production", path: ["SENTRY_DSN"] }
);Exporting a Typed Config Singleton
The end goal: the rest of your codebase never touches process.env. It imports a single validated, typed env object.
- Centralizing access means TypeScript autocompletes every key and flags typos at compile time.
- Refactoring a variable name becomes a single-file change.
- Tests can import a schema and feed mock objects instead of mutating global state.
// src/config.ts
import "dotenv/config";
import { z } from "zod";
const EnvSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
PORT: z.coerce.number().int().positive().default(3000),
DATABASE_URL: z.string().url(),
});
const parsed = EnvSchema.safeParse(process.env);
if (!parsed.success) {
console.error("❌ Invalid env:", parsed.error.flatten().fieldErrors);
process.exit(1);
}
export const env = Object.freeze(parsed.data);
// elsewhere: import { env } from "./config";Quick Check
Test your understanding of the type-safe config pattern.
Recap
You learned to make configuration and external input type-safe in modern Node.js:
- Why:
process.envand network data are untyped strings; validate once and fail fast. - Schemas: a library like Zod gives runtime validation plus inferred static types from one definition (
z.infer). - Env loading: coerce numbers, constrain with enums/defaults, load
.envvia--env-fileordotenv/configbefore reading. - safeParse: format
error.flatten().fieldErrorsandprocess.exit(1)on failure. - Beyond env: reuse schemas for request bodies, add
.transform()for derived config and.refine()for cross-field rules. - Result: export one frozen, typed
envsingleton the whole app imports.
Preguntas frecuentes
¿La lección «Configuración de entorno con seguridad de tipos y validación en tiempo de ejecución» es gratis?
Sí — el texto completo de «Configuración de entorno con seguridad de tipos y validación en tiempo de ejecución» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Node.js Backend Development Bootcamp, actualiza a CoddyKit PRO. El curso de Node.js Backend Development Bootcamp incluye 4 lecciones en total.
¿Qué aprenderé en «Configuración de entorno con seguridad de tipos y validación en tiempo de ejecución»?
Valide las variables de entorno y las entradas externas en tiempo de ejecución mediante bibliotecas de esquemas inferidas en tipos estáticos. Practicas Node.js Backend Development Bootcamp con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Node.js Backend Development Bootcamp?
No se requiere experiencia previa. Node.js Backend Development Bootcamp en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Configuración de entorno con seguridad de tipos y validación en tiempo de ejecución»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Node.js Backend Development Bootcamp?
Sí. Cada lección de Node.js Backend Development Bootcamp incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Migración de CommonJS a módulos ES nativos
- Configuración de tsconfig para proyectos backend de Node
- Configuración de entorno con seguridad de tipos y validación en tiempo de ejecución
- Iteración rápida con tsx, recarga en caliente y mapas de origen