Validate where untrusted data enters
A frontend form can prevent an empty title. An HTTP client can bypass that form. The API must validate the data it actually receives.
TypeScript helps developers call functions correctly within the program. Its types disappear at runtime. A body containing {"title":42} arrives regardless of the interface you wrote in the frontend.
Taskboard validates incoming data before using it in business operations. A schema describes accepted fields, lengths, formats, and enum values. Validation produces either a usable value or a controlled failure.
// Illustrative runtime schema.
const createTaskInput = z.object({
title: z.string().trim().min(1).max(200),
});
The exact limits are defined in the source. The mechanism matters: the API checks the runtime value rather than assuming that a TypeScript annotation makes it valid.
Several checks answer different questions
Input validation asks whether a title is the right shape. Authorization asks whether Maya can create tasks in the workspace. Database constraints ask whether the resulting row can coexist with other committed data.
A valid UUID can identify another tenant's project. Validation accepts its format, while authorization and a tenant-scoped query reject its use. No single check replaces the others.
Expected failures deserve intentional responses. A malformed request is different from a stale version conflict or an unavailable database. Returning the same generic error for all three prevents the frontend from offering the appropriate recovery.
Unexpected exceptions deserve a safe response and an internal log with the request identifier. The API must not return stack traces, database credentials, or raw provider messages to callers.
Reject ambiguous input
Strings such as "false" and "0" are truthy in JavaScript. Blind coercion can accept behavior the caller did not intend. Dates, numeric pagination limits, and optional values need explicit parsing rules.
Missing, empty, and null are also different states. A patch may omit an assignee to preserve it or send null to remove it. The input contract must explain that difference.
Can runtime validation prove that an assigned user belongs to the workspace?
No. Validation can verify the identifier's format. Membership is a database fact that requires a scoped lookup or an appropriate relational constraint.
Validation establishes usable input. Authorization and database constraints establish different guarantees about what that input may do.
Read api/src/tasks.ts Read api/src/security.ts