Course navigationPhase 12
Volume 04 · Phase 12

DTOs, Zod & Input Validation

Validate untrusted input with Zod and DTO/boundary patterns while keeping transport validation distinct from business rules.

14lessons + labs
5klesson words
7source references
What you will ship

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.

Figure 12.1 — Where this phase lives in TaskFlow
untrusted JSON/query
schema parse
typed validated value
business rules
persistence
Trace the flow left-to-right for implementation and right-to-left for debugging. At every arrow, ask what data crosses the boundary and what proves it is trustworthy.
Instructor mental model

Principles to keep while coding

01

Validate external input at the application boundary before business logic trusts it.

02

Use schemas to produce both runtime checks and reusable TypeScript types where appropriate.

03

Distinguish transport validation from domain/business rules because they fail for different reasons.

04

Return validation errors that help clients correct input without exposing internal implementation details.

Code-along path

Guided phase lab

Do these in order. Each step should end with evidence before the next one starts.

  1. 01

    List the exact fields accepted by create-task and reject unknown/invalid shapes deliberately.

  2. 02

    Create a Zod schema and derive the TypeScript type instead of maintaining two divergent contracts.

  3. 03

    Integrate validation at the NestJS boundary before the service receives data.

  4. 04

    Create a partial update schema carefully and reason about omitted vs nullable fields.

  5. 05

    Validate query parameters such as page size/sort values instead of trusting strings.

  6. 06

    Send malformed requests and verify the error contract is actionable but does not expose internals.

Debugging studio

Failures you should know how to diagnose

Failure drill 1

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.

Failure drill 2

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.

Failure drill 3

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.

Failure drill 4

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.

Watch-order style

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.

01

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

2 min
02

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

2 min
03

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

2 min
04

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

2 min
05

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 /

2 min
06

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

2 min
07

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

2 min
08

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

2 min
09

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

2 min
10

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

2 min
LAB

Guided Build Lab

Purpose Validate untrusted input with Zod and DTO/boundary patterns while keeping transport validation distinct from business rules.

3 min
LAB

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.

2 min
LAB

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?

2 min
LAB

Mastery Capstone Lab

Integrate Phase 12 without following a transcript.

2 min
Practice ladder

Exercises that increase independence

Guided

List the exact fields accepted by create-task and reject unknown/invalid shapes deliberately.

Practice

Integrate validation at the NestJS boundary before the service receives data.

Debug

reproduce and diagnose — Assuming TypeScript validates runtime JSON.

Independent

deliver Reusable create/update/query schemas integrated into NestJS request handling with useful validation errors.

Explain-back

Can distinguish type checking from runtime validation and Can build create/update/query schemas.

Mastery gate

Do not leave the phase only because you reached the bottom

Phase exit criteria

Knowledge check

Scenario and concept quiz

These questions test engineering choices rather than whether you remember a lesson title.

0/0 correct · 0/8 answered
01Which practice best matches Phase 12's engineering goal?
02Which practice best matches Phase 12's engineering goal?
03Which practice best matches Phase 12's engineering goal?
04Which practice best matches Phase 12's engineering goal?
05You are working through the Phase 12 lab. Which next action is the most disciplined step?
06You are working through the Phase 12 lab. Which next action is the most disciplined step?
07You are working through the Phase 12 lab. Which next action is the most disciplined step?
08You are working through the Phase 12 lab. Which next action is the most disciplined step?
Source snapshot

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 = @{