All notes

Validate at the boundary

Parse untrusted data once, at the edge, into a type the rest of your system can trust — and stop re-checking it everywhere else.

#engineering#types

Most production bugs we get called in to fix aren't clever. They're a value that wasn't the shape someone assumed it was — a null where a string was expected, a date that's actually a string, a number that arrived as "42". The fix is almost never more defensive code scattered through the call stack. It's a boundary.

Parse, don't validate

The idea is old and worth repeating: do the checking once, at the edge where untrusted data enters — an API response, a form, a file, an env var — and turn it into a value whose type guarantees it's correct from then on.

const Config = z.object({
  port: z.coerce.number().int().positive(),
  host: z.string().min(1),
});

// Throws here, at startup — not three layers deep at 2am.
const config = Config.parse(process.env);

Everything downstream takes config and can stop asking "but what if port is a string?" The question has already been answered, structurally. The compiler carries the proof.

Why it pays off

  • Failures move to the edge. Bad input fails loudly, at the boundary, with a clear message — not silently, halfway through a write.
  • The core gets simpler. No repeated null-checks, no "just in case" re-validation. The types say what's true.
  • Refactors stay safe. Change the shape in one place and the type system walks you to every consumer that needs updating.

It's the same instinct behind every reliable system we build: decide what's true at one well-chosen place, and let the rest of the code rely on it.

Working on something like this?

Start a conversation