Input and resource controls
Validate every request shape
Parse parameters, headers, bodies, and content types into bounded application values before business logic uses them.
JSON syntax only proves the body is JSON. It says nothing about the values your operation can safely accept. A valid JSON body can contain 50,000 line items or a negative total.
Validate structure and bounds
Validate types, lengths, ranges, formats, nesting depth, array size, and allowed fields. A schema library keeps the rules declarative and testable:
import { z } from 'zod'
const createInvoice = z.object({
customerId: z.string().regex(/^cus_[a-z0-9]{12}$/),
items: z.array(z.object({
sku: z.string().max(64),
quantity: z.number().int().min(1).max(999),
})).min(1).max(100),
total: z.number().positive().max(1_000_000),
}).strict()
.strict() rejects unknown fields. Rejecting unknown fields is safer for authority-bearing input, but tolerant readers may be useful for harmless provider additions.
Reject unsupported content types and ambiguous duplicates too — two total fields in one body, or a text/plain request your framework helpfully parses as JSON anyway.
Verify rejection at the boundary
Test the exact limits, then one step beyond each:
curl -i -X POST https://api.example.com/invoices \
-H "Content-Type: application/json" \
-d '{"customerId":"cus_ab12cd34ef56","items":[],"total":-500}'
# HTTP/1.1 400 Bad Request
# {"error":"items: at least 1 entry; total: must be positive"}
Then confirm no invoice row was created. A 400 that still wrote a partial record is worse than no validation at all, because it looks safe from the outside.
Validate again off the HTTP path
Apply validation again when a message arrives from a queue or third party. Also verify the queue consumer, because an internal producer or old message may bypass the current HTTP schema entirely. Messages enqueued last year under an older schema will replay against today’s assumptions.
Validate after decoding but before costly business work begins. Parsing 50,000 line items just to reject them late still costs you the parse, so put array-size limits as early as the body parser allows.
Record the accepted invoice schema and its numeric, size, and nesting limits. Test the exact boundaries plus one value beyond each limit, and prove rejected input creates no invoice.
Lesson completed