Skip to content
Node.js6 min read

Building Validated REST APIs with Fastify and JSON Schema

Fastify treats validation as part of the route, not an afterthought. How to use JSON Schema to make bad input impossible and responses faster at the same time.

Most API bugs we get called in to fix are not clever. They are a string where a number was expected, a missing field that arrived as undefined and travelled four layers deep before exploding, or an endpoint happily accepting fields it should have rejected. Fastify's answer is to make a JSON Schema part of every route definition, so malformed input is rejected at the front door with a clear 400 — before your handler runs at all.

Schemas live on the route

js
const createUserSchema = {
  body: {
    type: 'object',
    additionalProperties: false,
    required: ['email', 'name'],
    properties: {
      email: { type: 'string', format: 'email' },
      name: { type: 'string', minLength: 1, maxLength: 100 },
      marketingOptIn: { type: 'boolean', default: false },
    },
  },
};

fastify.post('/users', { schema: createUserSchema }, createUser);

Two details in that schema do a lot of work. additionalProperties: false rejects fields you did not ask for, which blocks mass-assignment bugs where a client sneaks in role: 'admin'. And default values are applied during validation, so your handler never needs the defensive marketingOptIn ?? false dance.

Be aware of type coercion as well: Fastify's default Ajv configuration converts compatible values rather than rejecting them, so the string '5' passed where an integer is declared arrives in your handler as the number 5. For querystrings, where everything starts life as a string, that is exactly what you want; for JSON bodies you may prefer strictness, and the coerceTypes option is yours to change when registering the app. Whichever you choose, decide once and write it down — mixed expectations about coercion are a reliable source of confusing bug reports.

Response schemas make you faster, not just safer

The less-known half of the story is response serialisation. If you declare a response schema, Fastify compiles it into a specialised serialiser via fast-json-stringify, which significantly outperforms generic JSON.stringify. Just as valuably, only the properties in the schema are emitted. Add a passwordHash column to your user model next year and it still cannot leak, because the serialiser was never told about it.

js
const userResponse = {
  200: {
    type: 'object',
    properties: {
      id: { type: 'string' },
      email: { type: 'string' },
      name: { type: 'string' },
    },
  },
};

Keeping schemas maintainable

Inline schemas get unwieldy quickly. Register shared definitions once with fastify.addSchema and reference them by $ref, so the user object is defined in exactly one place. For TypeScript projects, json-schema-to-ts or TypeBox derive static types from the schemas themselves, which means the compiler and the runtime validator can never drift apart — a genuinely rare property in this ecosystem.

  • Define one schema module per resource, exporting body, params and response schemas.
  • Always set additionalProperties: false on request bodies.
  • Validate params and querystring too — IDs deserve patterns, page sizes deserve maximums.
  • Generate OpenAPI docs from the same schemas with @fastify/swagger rather than writing them twice.

Errors your clients can act on

Out of the box, validation failures return a terse message aimed at developers. Attach a custom errorHandler that maps validation errors into your API's standard error envelope, keeping field paths intact so frontend forms can highlight the offending input. This is a one-off investment that every consumer of your API benefits from forever. We also log a sample of validation failures at debug level: a sudden spike on one field usually means a client release has shipped a breaking change, and catching that in your own telemetry beats hearing about it from their support team.

Schema-first routes change how it feels to work on an API. Handlers shrink to business logic, review conversations move from defensive coding to behaviour, and the docs stop lying. If your Fastify routes are still validating by hand, an afternoon of refactoring pays for itself almost immediately.

Building an API and want it right first time? STRCLI designs and builds production Node.js backends for UK businesses.

Start your project

Have an idea? Let's ship it together.

Tell us what you're building — we'll reply within one business day with an honest take and a clear next step.