The Data Triangle
Announcing nestjs-zod v5
Great news! nestjs-zod
version 5 is now available. You can install it today:
npm install nestjs-zod@latest
This release includes exciting new features (including Zod v4 support 🎉), bug fixes, and some minor breaking changes. Check out the MIGRATION.md file or release notes for the full details.
But today, I want to share what excites me most about nestjs-zod
and use it as an example to introduce a concept I call "The Data Triangle."
What is The Data Triangle?
nestjs-zod
solves a fundamental problem with API development: keeping the run-time, compile-time, and "docs-time" representations of api responses in-sync. I couldn't find an existing name for this principle, so I propose this name: "The Data Triangle." (Software engineers love shape-based names for software principles 😅)

The Data Triangle enables this powerful guarantee:
If the application compiles, the api is guaranteed to respond with data that matched the api docs.
This is a big deal
In my experience, a significant portion of bugs stem from APIs returning data that doesn't match their documented contract. When you have compile-time guarantees that your returned data matches your API documentation, you eliminate an entire category of bugs.
When is The Data Triangle violated?
The Data Triangle principle is violated when it's possible in an architecture for the run-time, compile-time, or docs-time types to become out of sync.
Here are a couple examples of when this can happen:
Not generating API documentation from your implementation
Systems that don't generate API documentation from the implementation violate The Data Triangle
If the API documentation is maintained separately from the implementation, then it's likely for the implementation to get out of sync with the documentation
Note this isn't incompatible with "Contract-first design". One can still follow "Contract-first design" and design the API before the implementation starts. However, once the implementation starts, the documentation exposed to consumers should be generated.
Using a language without a compile step
The Data Triangle needs a compile-step by definition (i.e. TypeScript). The "compile-time" schema is one of the three points on the triangle.
It enables the main guarantee of this principle; the api docs are accurate if the compilation passes.
JavaScript, Ruby (without Sorbet), PHP (without static type-checking), etc. do not have a compile step and can not tell you about api docs violations until the application is already running.
I know in languages without static types, you can write automated tests to test your API contract. But in this author's opinion, static type-checking is still the most robust way to find data-related bugs.
Repeating your data schema in multiple representations
The Data Triangle is violated in a system when the data schema declarations are repeated using different formats (e.g. OpenAPI, TypeScript types, validation library formats, etc.)
This is because the repeated schema representations can drift apart from each-other. And if it can drift, it will drift.
The key is to write one representation of your data and treat it as the source of truth, and generate the other two formats.
For example, in zod
you write the runtime representation and generate the compile-time version via z.infer
and the docs-time version via z.toJSONSchema
class-validator
The class-validator
library is commonly used in NestJS, but it unfortunately violates The Data Triangle principle because its compile-time, run-time, and docs-time types can easily become inconsistent.
Consider this problematic example:
export class Post {
@ApiProperty({
type: Boolean,
})
@IsString()
rating: number;
}
This code has three different type definitions:
- Run-time:
string
(validation expects a string) - Compile-time:
number
(TypeScript type) - Docs-time:
boolean
(OpenAPI schema)
TypeScript won't catch this mismatch, runtime validation will fail unexpectedly, and the API docs will be misleading.
And when an inconsistency is possible, it will happen.
How to adhere to The Data Triangle?
Here's a concrete example using nestjs-zod
:
import { z } from 'zod'
import { createZodDto, ZodResponse } from 'nestjs-zod'
class PostDto extends createZodDto(z.object({
title: z.string().describe('The title of the post'),
content: z.string().describe('The content of the post'),
authorId: z.number().describe('The ID of the author of the post'),
})) {}
@Controller('posts')
export class PostsController {
@Get(':id')
@ZodResponse({ type: PostDto })
getById(@Param('id') id: string) {
return {
title: 'Hello',
content: 'World',
authorId: 1,
};
}
}
The key here is the @ZodResponse
decorator, which performs three critical functions simultaneously:
- Run-time Validation: It validates the response against
PostDto
and throws errors for invalid data - Compile-time Type Checking: It ensures TypeScript catches any mismatches between your return type and
PostDto
- Docs Generation: When used with
nestjs/swagger
, it automatically generates OpenAPI schemas from yourPostDto
By using a single schema and a single decorator for all three representations of your data, you eliminate the possibility of them becoming out of sync.
Note that The Data Triangle principle isn't limited to NestJS. You can apply this approach in any framework or language that supports compile-time type checking. Your API consumers will thank you 🙂
Taking The Data Triangle Even Further
The Data Triangle principle can be extended even further to implement a concept called "End-to-end type safety"
With "End-to-end type safety", API consumers will get compile-time errors when they try to use your API incorrectly. Frameworks like tRPC
, react-router
, and next.js
provide this capability.
With nestjs-zod
, you can achieve complete end-to-end type safety by generating a typed client from your OpenAPI documentation. Tools like openapi-ts
can create fully typed API clients that give consumers compile-time guarantees when calling your API.
Final Thoughts
If you use NestJS, you can use nestjs-zod
to follow The Data Triangle. But even if you don't use NestJS or nestjs-zod
, The Data Triangle (ensuring your run-time, compile-time, and docs-time types can not get out of sync) is a valuable architecture principle that improves software reliability.