All posts
4 min read

Form Validation Explained: Client-Side, Server-Side, and Why You Need Both

Validation checks that what users type makes sense before you save it. The difference between browser-side and server-side validation, why only the server's counts for security, built-in HTML validation, sharing rules with a schema, and writing error messages people understand.

getting startedsecurityno-codebeginner

Every form in your app — sign-up, checkout, settings, contact — needs validation: checking that what people submit makes sense before you save or act on it. AI-built apps often have validation that looks thorough, and protects almost nothing. The difference is where it runs.

Two places validation happens

Client-side: in the browser

Checks that run in the user's browser, before the form is sent:

  • "Email is required."
  • "Password must be at least 12 characters."
  • Red outlines and messages appearing as you type.

Purpose: a good user experience. Instant feedback, no waiting for the server.

Server-side: on your backend

Checks that run on your server when the data arrives, before it's saved.

Purpose: correctness and security.

Why only the server's validation counts

Client-side validation is trivially bypassed. Anyone can:

  • Turn off JavaScript.
  • Edit the page in DevTools and remove the required attribute.
  • Skip your form entirely and send a request straight to your API with any data they like — curl, a script, or a browser's Network tab "edit and resend."

So the browser's validation is a courtesy to honest users. The server must re-check everything, as if the front end didn't exist. (Frontend vs Backend.)

A classic AI-built-app bug: the sign-up form limits usernames to 20 letters, but the API accepts 50,000 characters of anything — including HTML that breaks every page it's displayed on. (What Is XSS?.)

What to validate on the server

For every field:

  • Present? Required fields exist.
  • Right type? A number is a number; a date is a date.
  • Right format? An email looks like an email; a postcode fits the pattern.
  • Right size? Minimum and maximum lengths; numbers within a sensible range. (No negative quantities, no 10 MB "names.")
  • Allowed values? A plan field is one of free, pro, team — nothing else.
  • Allowed for this user? The user can't set fields they shouldn't: isAdmin, price, ownerId, credits. Only accept the fields the form is meant to change. (Authentication vs Authorization.)

And some checks only the server can do:

  • Is the email already taken?
  • Does this coupon exist and is it still valid?
  • Is the item in stock?

Built-in browser validation

HTML has free validation built in — use it for the client-side part:

<form>
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required>

  <label for="age">Age</label>
  <input id="age" name="age" type="number" min="13" max="120">

  <label for="username">Username</label>
  <input id="username" name="username" required minlength="3" maxlength="20"
         pattern="[a-z0-9_]+">

  <button>Sign up</button>
</form>

type="email" also brings up the right keyboard on phones. (Regular Expressions for Beginners explains pattern.)

One set of rules, used in both places

Writing the same rules twice — once for the browser, once for the server — means they drift apart. The modern approach is a schema: define the rules once, and use them in both places. In TypeScript projects, Zod is the common choice:

import { z } from "zod"

export const signUpSchema = z.object({
  email: z.string().email(),
  username: z.string().min(3).max(20).regex(/^[a-z0-9_]+$/),
  age: z.number().int().min(13).max(120),
})

The browser uses it for instant feedback; the server uses the same schema to check the request before touching the database. (Validating Input With Zod goes further.)

Error messages people understand

Validation is also a conversation with your users. Good messages:

  • Say what's wrong and how to fix it. "Password must be at least 12 characters" beats "Invalid password."
  • Appear next to the field, not only in a banner at the top.
  • Keep what the user typed. Don't clear the whole form because one field was wrong.
  • Don't blame. "Please enter a date in the future" beats "Invalid input!"
  • Are accessible — connected to the field for screen readers, not just shown as a red border. (Web Accessibility Basics.)
  • Don't reveal too much. On login and password reset, don't confirm whether an email has an account.

Don't over-validate

Some validation annoys real users for no benefit:

  • Names — people's names contain apostrophes, hyphens, spaces, accents, and non-Latin characters. Don't restrict them to A–Z.
  • Email — perfect email validation is impossible. Do a light check and send a confirmation email.
  • Phone numbers and addresses — formats vary enormously between countries.
  • Password rules — long passphrases beat forced symbol-and-number combinations. Allow paste, so password managers work.

The checklist

  • Every form's data is validated again on the server
  • Types, formats, lengths, and ranges are checked
  • Users can't set protected fields
  • Rules are defined once (a schema) and shared
  • Errors are specific, next to the field, and keep the user's input
  • Names, emails, and phone numbers aren't over-restricted

EasySpawn runs your app's backend as a real server, so server-side validation has somewhere to live — and Claude Code can test it by sending the bad requests an attacker would. See how it works for AI-built apps or join the waitlist.

Related: SQL Injection Explained · What Is CRUD? · A Security Checklist for Vibe-Coded Apps

Keep reading