A single unvalidated endpoint — one handler that reads req.body.email directly into a database query without a validation step — is all an attacker needs to trigger injection (CWE-20, OWASP A03:2021), corrupt data with unexpected types, or crash the application with oversized inputs. OWASP A08:2021 (software and data integrity failures) also applies when schema-less input reaches business logic unguarded. TypeScript types do not enforce runtime shapes: JSON.parse gives you any, and type assertions are compile-time fiction.
Critical because even a single unvalidated endpoint is an injection and data-corruption vector — the entire API's trust boundary is defined by its weakest input handler.
Add runtime validation via Zod to every route handler that accepts input. Define schemas in src/lib/validations/ and import them into route files:
// src/lib/validations/user.ts
export const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
})
// app/api/users/route.ts
const result = createUserSchema.safeParse(await req.json())
if (!result.success) {
return apiError('VALIDATION_ERROR', result.error.message, 400)
}
Also validate path parameters — confirm that ID segments are valid UUIDs (z.string().uuid()) before using them in database queries. A validation schema is not optional polish; it is your first defense against injection and your contract with every caller.
saas-api-design.api-security.input-validation-allcriticalparse() or safeParse(), Yup validate(), Joi validate(), class-validator validate(), custom validation functions, or TypeScript type assertions used in place of runtime validation. Also check: are query parameters validated (not just body)? Are path parameters validated (e.g., is an ID checked to be a valid UUID before use)? Look specifically for handlers that use req.body.fieldName or params.id directly without any validation step.req.body, req.query, or req.params directly in database queries or business logic without validation. Even a single unvalidated endpoint constitutes a failure. Must not pass when validation exists on most routes but even 1 endpoint accepts raw input.app/api/ route files to every endpoint that accepts input. The recommended approach for TypeScript projects is Zod: define a schema for each request shape, then call schema.safeParse(req.body) and return a 400 if parsing fails. Keep schemas in a dedicated lib/validations/ or schemas/ directory and import them into route handlers. Also validate path parameters — check that IDs are valid UUIDs before querying the database. Input validation is your first defense against injection attacks, data corruption, and application errors from unexpected input.