0Pricing
Node.js Backend Development Bootcamp · Leçon

Configuration d’environnement sûre du point de vue des types et validation à l’exécution

Validez à l’exécution les variables d’environnement et les entrées externes avec des bibliothèques de schémas inférées en types statiques.

Configuration d’environnement sûre du point de vue des types et validation à l’exécution est une leçon Node.js Backend Development Bootcamp gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Node.js Backend Development Bootcamp, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Node.js Backend Development Bootcamp comprend 4 leçons au total.

Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.

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.PORT might be "3000", "", or missing entirely.
  • A typo like DATABSE_URL silently yields undefined.
  • TypeScript types process.env as Record<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 failure

Schema 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 interface that 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" into 3000.
  • 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 env object instead of touching process.env directly.
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.

  • safeParse returns { success: true, data } or { success: false, error } — it never throws.
  • error.flatten().fieldErrors groups 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); // number

Loading .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=.env flag — no dependency needed.
  • If you use dotenv, call dotenv/config at the very top, since ESM imports are hoisted and evaluated first.
  • Never commit real secrets; commit a .env.example documenting 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 parse the incoming JSON.
  • On failure, respond with 400 instead 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 path so 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.env and 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 .env via --env-file or dotenv/config before reading.
  • safeParse: format error.flatten().fieldErrors and process.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 env singleton the whole app imports.

Questions Fréquemment Posées

La leçon « Configuration d’environnement sûre du point de vue des types et validation à l’exécution » est-elle gratuite ?

Oui — le texte complet de « Configuration d’environnement sûre du point de vue des types et validation à l’exécution » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Node.js Backend Development Bootcamp, passe à CoddyKit PRO. Le cours Node.js Backend Development Bootcamp comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Configuration d’environnement sûre du point de vue des types et validation à l’exécution » ?

Validez à l’exécution les variables d’environnement et les entrées externes avec des bibliothèques de schémas inférées en types statiques. Tu pratiques Node.js Backend Development Bootcamp avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Node.js Backend Development Bootcamp ?

Aucune expérience préalable n'est requise. Node.js Backend Development Bootcamp sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.

Combien de temps prend la leçon « Configuration d’environnement sûre du point de vue des types et validation à l’exécution » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Node.js Backend Development Bootcamp ?

Oui. Chaque leçon Node.js Backend Development Bootcamp inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Migrer de CommonJS vers les modules ES natifs
  2. Configurer tsconfig pour les projets backend Node
  3. Configuration d’environnement sûre du point de vue des types et validation à l’exécution
  4. Itération rapide avec tsx, rechargement à chaud et mappages de sources
← Retour à Node.js Backend Development Bootcamp