[CODE] DDD in Practice

Domain-Driven Design with Zod: Design by Contract in TypeScript

Using Zod schemas as runtime contracts in a TypeScript project. How DBC principles help catch data shape errors at build time rather than runtime.

(updated 2026-02-15) 1 min read
typescript ddd zod architecture

Domain-Driven Design with Zod

One of the most underrated patterns in TypeScript development is using Zod not just for validation, but as a Design by Contract mechanism.

The Problem with Runtime Surprises

In a statically typed language, you’d expect type errors to surface at compile time. But when data crosses the boundary from external sources — API responses, MDX frontmatter, form inputs — TypeScript’s compile-time guarantees break down.

// This compiles fine, but might fail at runtime
const article = await getArticle(slug) // Type: Article | null
article.title // What if article is null?

Zod as Your Contract Enforcement Layer

import { z } from 'zod'

export const ArticleSchema = z.object({
  slug:        z.string().min(1).regex(/^[a-z0-9-]+$/),
  title:       z.string().min(1).max(120),
  publishedAt: z.date(),
  tags:        z.array(z.string().min(1)).min(1),
})

export type Article = z.infer<typeof ArticleSchema>

The key insight: the schema is the contract. If the data doesn’t conform, it throws immediately at the boundary — not deep inside your business logic.

Build Time vs Runtime

With Astro Content Collections, Zod validation runs at build time for MDX frontmatter. A malformed frontmatter field fails the build, preventing invalid data from ever reaching production.

That’s the dream: turning runtime surprises into compile-time certainties.