Why Express + TypeScript + Zod?
Express by itself is untyped JavaScript, req.body is any, and nothing stops a malformed request from reaching your business logic. TypeScript alone doesn't fix that either, types are checked at compile time and stripped away entirely before the code runs, they can't catch bad data arriving over the network at 2am.
Zod is a runtime validation library, .parse() (or .safeParse()) actually inspects the value while the program is running and throws (or reports) if it doesn't match, exactly what TypeScript's compile-time types can't do on their own.
One schema, two guarantees
z.infer<typeof CreateTaskSchema> derives a TypeScript type directly from the schema, so the compile-time type and the runtime check can never drift apart, there's one source of truth instead of a hand-written interface that quietly stops matching what you actually validate.
Untyped Express Express + TypeScript Express + TypeScript + Zod
βββββββββββββββββ ββββββββββββββββββββββ ββββββββββββββββββββββββββββ
req.body: any req.body: any (still!) req.body validated + typed
No compile-time Compile-time types only, Compile-time types AND
checks at all nothing checks the network a runtime boundary check
NOTE
TypeScript's own types genuinely disappear when your code compiles to JavaScript, they're a build-time tool. Zod schemas are ordinary JavaScript objects that keep working after compilation, that's why they're the right tool for validating anything crossing a real boundary: an HTTP request, a file read from disk, a response from another service.