Validate external input at the application boundary before business logic trusts it.
Course navigationPhase 12⌄
DTOs, Zod & Input Validation
Validate untrusted input with Zod and DTO/boundary patterns while keeping transport validation distinct from business rules.
Phase outcome and project increment
Learning outcome
Validate untrusted input with Zod and DTO/boundary patterns while keeping transport validation distinct from business rules.
Concrete deliverable
Reusable create/update/query schemas integrated into NestJS request handling with useful validation errors.
Principles to keep while coding
Use schemas to produce both runtime checks and reusable TypeScript types where appropriate.
Distinguish transport validation from domain/business rules because they fail for different reasons.
Return validation errors that help clients correct input without exposing internal implementation details.
Guided phase lab
Do these in order. Each step should end with evidence before the next one starts.
- 01
List the exact fields accepted by create-task and reject unknown/invalid shapes deliberately.
- 02
Create a Zod schema and derive the TypeScript type instead of maintaining two divergent contracts.
- 03
Integrate validation at the NestJS boundary before the service receives data.
- 04
Create a partial update schema carefully and reason about omitted vs nullable fields.
- 05
Validate query parameters such as page size/sort values instead of trusting strings.
- 06
Send malformed requests and verify the error contract is actionable but does not expose internals.
Failures you should know how to diagnose
Assuming TypeScript validates runtime JSON
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Duplicating client and server validation rules without a sharing strategy
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Putting database existence checks into pure syntax schemas
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Accepting arbitrary unknown keys into persistence calls
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Lessons
The source chapters come first. Every phase now ends with a guided build, a debugging workshop, and an independent challenge so reading turns into transferable skill.
Runtime Validation Mental Model
Runtime Validation Mental Model Why TypeScript types are not runtime validation and where trust boundaries belong Phase 12 - DTOs, Zod & Input Validation Volume 04 - Professional Backend Arc
Zod 4 and NestJS 12 Standard Schema
Zod 4 + NestJS 12 Standard Schema Install Zod, lock the version, and use NestJS 12 built-in schema-first validation Phase 12 - DTOs, Zod & Input Validation Volume 04 - Professional Backend A
Task Schemas and Inferred DTOs
Task Schemas & Inferred DTOs Create/update schemas, UUIDs, enums, nullable fields, strict objects, and DTO inference Phase 12 - DTOs, Zod & Input Validation Volume 04 - Professional Backend
Controller Integration and Validation Pipeline
Controller Integration & Validation Pipeline Attach schemas to @Body and @Param and keep controllers/services focused Phase 12 - DTOs, Zod & Input Validation Volume 04 - Professional Backend
Create Task Validation
Create Task Validation Validate POST bodies before Project lookup or Prisma persistence Phase 12 - DTOs, Zod & Input Validation Volume 04 - Professional Backend Architecture milestone POST /
Update Task Validation
Update Task Validation PATCH semantics, optional fields, nullable values, enums, and empty-update rejection Phase 12 - DTOs, Zod & Input Validation Volume 04 - Professional Backend Architect
Path Query and Unknown Field Validation
Path, Query & Unknown-Field Validation UUID parameters, query coercion mental model, strict objects, and layered boundary defense Phase 12 - DTOs, Zod & Input Validation Volume 04 - Professi
Validation Errors Testing and Debugging
Validation Errors, Testing & Debugging Diagnose failures by layer and prove malformed input never reaches persistence Phase 12 - DTOs, Zod & Input Validation Volume 04 - Professional Backend
Shared Schemas and Boundary Design
Shared Schemas & Boundary Design When to share validation, why transport schemas are not Prisma models, and avoiding premature packages Phase 12 - DTOs, Zod & Input Validation Volume 04 - Pr
Hands On Lab and Phase Exit
Hands-On Lab & Phase Exit Request matrix, exercises, oral exam, and completion checklist Phase 12 - DTOs, Zod & Input Validation Volume 04 - Professional Backend Architecture milestone insta
Guided Build Lab
Purpose Validate untrusted input with Zod and DTO/boundary patterns while keeping transport validation distinct from business rules.
Debugging Workshop
Goal Learn to diagnose this phase's failures systematically instead of reinstalling packages, changing random configuration, or copying a fix without understanding it.
Phase Synthesis & Independent Challenge
Mission Rebuild the capability of Phase 12 without following the exact code line-by-line. The goal is transfer: can you use the ideas when the variable names, UI, or route shape changes?
Mastery Capstone Lab
Integrate Phase 12 without following a transcript.
Exercises that increase independence
List the exact fields accepted by create-task and reject unknown/invalid shapes deliberately.
Integrate validation at the NestJS boundary before the service receives data.
reproduce and diagnose — Assuming TypeScript validates runtime JSON.
deliver Reusable create/update/query schemas integrated into NestJS request handling with useful validation errors.
Can distinguish type checking from runtime validation and Can build create/update/query schemas.
Do not leave the phase only because you reached the bottom
Phase exit criteria
Scenario and concept quiz
These questions test engineering choices rather than whether you remember a lesson title.
Reference files
Attempt the work first. Use snapshots to compare a checkpoint, not as a substitute for understanding.
reference/TaskFlow_Validation_Snapshot/README.txt
TaskFlow Phase 12 validation reference snapshot This snapshot focuses only on the files changed by the validation phase. It assumes the Phase 11 Prisma schema, seed, PrismaService, TasksModule, and database configuration already exist. Run from apps/api: pnpm add zod@4.6.5 pnpm run build pnpm run start:dev Then run the PowerShell smoke tests in reference/tests/validation-smoke-tests.ps1.
reference/TaskFlow_Validation_Snapshot/apps/api/package.snippet.json
{
"dependencies": {
"zod": "4.6.5"
}
}reference/TaskFlow_Validation_Snapshot/apps/api/src/main.ts
import { StandardSchemaValidationPipe } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import {
FastifyAdapter,
NestFastifyApplication,
} from "@nestjs/platform-fastify";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
app.useGlobalPipes(new StandardSchemaValidationPipe());
app.enableCors({
origin: "http://localhost:3000",
});
reference/TaskFlow_Validation_Snapshot/apps/api/src/tasks/tasks.controller.ts
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Post,
} from "@nestjs/common";
import { TasksService } from "./tasks.service";
import {
createTaskSchema,
CreateTaskDto,
taskIdSchema,
updateTaskSchema,
UpdateTaskDto,
} from "./tasks.schemas";
reference/TaskFlow_Validation_Snapshot/apps/api/src/tasks/tasks.schemas.ts
import { z } from "zod";
import {
TaskPriority,
TaskStatus,
} from "../generated/prisma/client";
const taskTitleSchema = z
.string()
.trim()
.min(1)
.max(200);
const taskDescriptionSchema = z
.string()
.trim()
.max(2000)
.nullable();
export const taskIdSchema = z.uuid();
reference/TaskFlow_Validation_Snapshot/apps/api/src/tasks/tasks.service.ts
import {
Injectable,
NotFoundException,
} from "@nestjs/common";
import { Prisma } from "../generated/prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import {
CreateTaskDto,
UpdateTaskDto,
} from "./tasks.schemas";
@Injectable()
export class TasksService {
constructor(private readonly prisma: PrismaService) {}
findAll() {
return this.prisma.task.findMany({
orderBy: { createdAt: "desc" },
});
}reference/TaskFlow_Validation_Snapshot/tests/validation-smoke-tests.ps1
# Run from any PowerShell terminal while apps/api is running.
$base = "http://localhost:4000"
$projectId = "<PROJECT-ID-FROM-PHASE-11-SEED>"
# Valid create
$validCreate = @{
projectId = $projectId
title = "Validate TaskFlow input"
description = "Created through a Zod-validated endpoint."
} | ConvertTo-Json
Invoke-RestMethod `
-Method Post `
-Uri "$base/tasks" `
-ContentType "application/json" `
-Body $validCreate
# Invalid: blank title
$blankTitle = @{