SOFTWARE / SYSTEMS / AIEngineering news. Technical depth.
Architecture / 4 MIN READ

Split form validation across browser, API, and database without losing one contract

Use browser checks for interaction, API checks for authority, and database constraints for durable invariants without pretending one layer can do every job.

Form validation has three owners because it serves three different purposes. The browser can give immediate feedback when used carefully. The API authoritatively enforces security and business rules for every client. The database protects invariants under concurrency. Share descriptions and generated hints where helpful, but never make client validation the enforcement boundary or duplicate database error text into the interface.

Let the browser improve interaction

Use native input types, required fields, length bounds, and clear inline guidance for conditions the user can fix before submission. The HTML Standard constraint-validation model defines candidate controls, validity states, and interactive validation behavior. JavaScript can add cross-field feedback, but it should complement a usable form and must not assume the request is trusted.

Errors need text, programmatic association, and a path to correction. WCAG 2.2 guidance for Error Identification explains that detected input errors should identify the item and describe the error in text. Do not rely only on red borders, disappearing toast messages, or focus changes without explanation. Preserve entered values unless they are secrets that should be cleared.

Native browser validation can satisfy that requirement in many common combinations of browsers and screen readers, but the same WCAG guidance for Error Identification also notes important limitations: messages may be transient, may not scale well when zoomed, and often expose only the first error at a time. Treat native validation as a useful interaction aid, then test it with your target browsers and assistive technologies before relying on its exact behavior.

Browser checks may intentionally be more helpful than the server contract. A password-strength hint can update as the user types; the API remains authoritative because policy may change or depend on account state. Keep messages phrased around action: “Use at least one more word” is better than exposing an internal validator name.

Make the API the decision point

The API validates shape, authorization, tenant scope, canonical formats, current business state, and interactions between fields. It must do so for browser, mobile, CLI, imported data, and malicious direct requests. Normalize deliberately before uniqueness checks: trimming or case folding can change identity semantics, so document it rather than letting client libraries improvise.

Return a stable error envelope with a code, safe message, correlation ID, and optional field pointers. Separate field errors from form-level errors and concurrency conflicts. A field pointer might target /email; INVITATION_ALREADY_EXISTS may be a form error because it depends on current state. The client maps known codes to localized guidance and has a safe fallback for new codes.

Never pass raw database exception messages through. They can expose schema names, values, and implementation detail. Translate known constraint identifiers in a controlled mapping and log the internal cause with restricted access.

Let the database win races

Only the database sees concurrent commits at the storage boundary. Use NOT NULL, CHECK, unique, foreign-key, and exclusion constraints for invariants it can express. PostgreSQL constraints documentation describes these mechanisms and notes important semantics such as null handling in uniqueness and the limits of CHECK constraints for cross-row rules.

For example, the UI checks email syntax, the API canonicalizes and verifies invite authority, and a database unique constraint prevents two pending invitations for the same workspace and canonical address. Two requests can both pass the API precheck; one commit wins, and the other maps the named conflict to INVITATION_ALREADY_EXISTS.

Maintain one behavioral contract

Keep validation rules close to their authority and publish only the subset clients need for interaction. A shared schema can align basic lengths and formats, but current-state rules and database concurrency remain server-side. Version error codes additively and test that older clients render unknown codes sensibly.

Review one form with this matrix: rule, user feedback, API enforcement, storage constraint, error code, and concurrency test. Start with its uniqueness rule. Launch two identical submissions simultaneously, prove the database accepts one, and verify the loser receives a helpful stable error. That single test clarifies all three layers’ jobs.

Keep the mapping exhaustive for expected conflicts and treat an unknown constraint as a server error rather than mislabeling it as user input.

Review date: 2026-09-06

SOURCES & REVIEW

Sources are linked throughout this guide. Product capabilities can change; consult the linked documentation for your deployment.

Read our editorial approach ↗