← Recipes

阅读中文版本

Add Schema Validation to a Form

Define one schema, validate on the client for instant feedback, and reject invalid payloads on the server so no rules live only in the browser.

~30 minvalidationformstypescript

Task

Validate form input with a shared schema used by both the UI and the API handler.

When to use this

Client validation and server validation drift apart, so the UI accepts input the API later rejects with a cryptic error.

Prerequisites

  • A schema library such as Zod
  • A form or request handler

Steps

1. Declare fields, types, and constraints in a single schema module.

Expected output: One source of truth defines every rule.

2. Parse on submit and map issues to field-level messages.

npx tsx validate.ts --input form.json

Expected output: Users see precise errors next to the offending field.

3. Parse the same schema on the server and return 422 with the issue list.

Expected output: Invalid payloads never reach business logic.

4. Coerce and transform at the boundary rather than inside components.

Expected output: Stored data matches the declared types.

Troubleshooting

Problem

Duplicate schemas

Solution

Import the same module in client and server; never hand-copy rules into the handler.

Problem

Error messages leak internals

Solution

Map schema issues to user-facing text and log the raw issue server-side only.

Limitations

  • Client validation is for usability; it is not a security control and must never replace server checks.
  • Schema libraries differ in coercion behavior, so pick one and keep it consistent across the stack.

Context

Validate form input with a shared schema used by both the UI and the API handler. Client validation and server validation drift apart, so the UI accepts input the API later rejects with a cryptic error.

Steps

  1. Declare fields, types, and constraints in a single schema module. Expected: One source of truth defines every rule.
  2. Parse on submit and map issues to field-level messages. Expected: Users see precise errors next to the offending field.
  3. Parse the same schema on the server and return 422 with the issue list. Expected: Invalid payloads never reach business logic.
  4. Coerce and transform at the boundary rather than inside components. Expected: Stored data matches the declared types.
const schema = z.object({
  email: z.string().email(),
  age: z.coerce.number().int().min(18),
});
const parsed = schema.safeParse(input);
if (!parsed.success) return res.status(422).json(parsed.error.issues);

Verify

  • One source of truth defines every rule.
  • Users see precise errors next to the offending field.
  • Invalid payloads never reach business logic.
  • Stored data matches the declared types.

Limits

  • Client validation is for usability; it is not a security control and must never replace server checks.
  • Schema libraries differ in coercion behavior, so pick one and keep it consistent across the stack.