Find and fix weaknesses

Validate at the boundary

Treat external data as untrusted, parse it into a known shape, and enforce authorization and invariants before use.

Every request, file, webhook, database record, and third-party response can be malformed or hostile. Validate at the boundary: parse external data into a known shape before it gains any authority inside your code.

The order matters. Validation happens first, at the edge, once. Code past the boundary should receive typed, bounded, checked values — never the raw request.

What to check

For each field: type, length, range, format, and allowed values. A schema library makes this declarative:

import { z } from 'zod'

const CreateNote = z.object({
  title: z.string().min(1).max(200),
  body: z.string().max(50_000),
  visibility: z.enum(['private', 'shared']),
}).strict() // reject unknown fields

const result = CreateNote.safeParse(req.body)
if (!result.success) return res.status(400).json({ error: 'invalid input' })

Reject unknown fields when they create risk. The classic failure is a client sending role: "admin" and a careless spread operator copying it straight into the database.

curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:3000/api/notes \
  -H "Content-Type: application/json" \
  -d '{"title":"ok","body":"hi","visibility":"private","role":"admin"}'
# 400 — the unknown field bounced at the boundary

Valid is not the same as safe

Here is the gap validation fills. A create-note endpoint accepts a valid string title with five million characters. The JSON parses fine — it is a string, after all. But the request consumes memory, bloats every log line that echoes it, and fills logs before the database rejects it. Type-correct, and still a denial-of-service vector. The max(200) bound kills it at the door.

Also know what validation does not do. It does not replace safe parameterized queries, output encoding, or authorization; each solves a different problem. A perfectly validated note title can still belong to another user, and a valid string can still break an HTML page that renders it unescaped.

Strictness meets rollouts

A strict schema may reject a new client field during a rollout: the mobile app v2 sends tags, the server does not know it yet, and every v2 request bounces. That is not a reason to accept every unknown value forever. Version the contract deliberately — add the field to the schema before the client ships.

Exercise one boundary with five requests: valid, missing field, wrong type, oversized, and extra field. Save the response and the database state for each, including proof that rejected input caused no write. A 400 that still inserted a row is two bugs, not one.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →