Why validate at the boundary
Validating once at the trust boundary beats scattering null checks through every function. JSON Schema turns "this field must exist, be a number, within a range" into a machine-checkable contract.
Common keywords
| Keyword | Role |
|---|---|
| required | Mandatory fields |
| type | Base type (string/number/object/array…) |
| format | Semantic format (email/date-time/uuid) |
| minimum / maximum | Numeric range |
| pattern | Regex constraint |
Two pitfalls
- Schema is not a security boundary: it won't stop logic flaws or injection; use parameterized queries for SQL;
- Don't validate type only: also cap string length and array size, or you may blow up storage or memory.
Try it
Format and inspect JSON: JSON formatter.
Real-world cases: three kinds of dirty data a schema missed
- Types checked, formats not:
type: stringdoes not stop an empty string or a malformed email; addformat,minLengthandpattern. - Extra properties allowed: undeclared fields pass by default and get written silently. Set
additionalProperties: falseto surface contract mismatches early. - Shared schema with no version: change a schema reused via
$refand every consumer is affected at once. Version the schema and review changes for compatibility.
FAQ
Can JSON Schema replace business validation? No — it covers structure only; cross-field rules (end time after start time) still need code. What should a validation error return? A list of field paths and reasons so the frontend can point at the problem, not just "invalid parameters". Is frontend validation enough? No — it is a UX nicety; the server must validate independently. oneOf or anyOf? Use oneOf when you need to know which branch matched; anyOf is looser for plain structural constraints.
Layered adoption: where to validate
One schema can be reused in several places, each with a different job:
- Gateway / edge: coarse checks only (size, required fields, obvious type errors) to reject hostile traffic early;
- Service entry: full structural validation with field-level errors — the one layer that must enforce strictly;
- Before the database: integrity constraints (not null, unique, length) as the last defence against writes that bypass the app;
- Frontend: reuse the same schema for instant feedback — a UX aid, never a security boundary.
Versioning and compatible evolution
- Adding fields: optional additions are compatible; make one required only via a default, then tighten gradually;
- Type changes or renames: breaking — run a new version in parallel and warn on the old one;
- Version shared schemas: treat them as part of the API contract so one edit does not surprise every consumer;
- Generate, do not hand-copy: derive TS types or validation code from the schema so docs, types and implementation cannot drift.
Common misconceptions
- Treating the schema as a rules engine: cross-field logic and external lookups still need code;
- Validating requests but not responses: upstream payloads can be wrong too — validate critical dependencies to stop bad data spreading;
- Vague errors: "invalid parameters" forces trial and error; return field paths and reasons.
Keyword quick reference
- Type and range:
type,enum,minimum/maximum,minLength/maxLength; - Structure:
properties,required,additionalProperties,items,minItems; - Composition:
allOfall must hold,anyOfat least one,oneOfexactly one,notnegates; - Conditionals:
if/then/elseexpress "when this field is present, another constraint also applies"; - Reuse:
$defswith$reffor shared fragments instead of copy-paste drift.
Performance notes
Two things matter at scale: cache compiled validators instead of rebuilding the schema per request, and avoid deeply nested oneOf trees that can blow up combinatorially. On very high-throughput paths, do a cheap field check first and run full validation only when needed.
Working with the type system
Schema validation and static types solve different problems: types stop you writing the wrong thing at compile time, schemas stop the wrong thing arriving at runtime. Generate both from one definition, or you end up with types that permit what runtime rejects. Replay real traffic through the validator periodically to catch drift.