Fastify Schema Validation and fast-json-stringify for Type-Safe APIs
Stop letting invalid request bodies reach your handlers. Learn how Fastify schema validation and fast-json-stringify improve API safety and throughput.
02 Sept 2025, 13:46 UTC

Invalid payloads should never reach your handler
Imagine a POST endpoint that expects a user email. Without validation, a missing or malformed body travels straight to your route handler. The handler tries to read request.body.email, gets undefined, and throws a runtime error. You end up writing defensive checks inside every handler, which clutters business logic and still misses edge cases.
Fastify solves this by validating the request body against a JSON Schema at parse time. If the payload does not match, Fastify rejects it with a 400 response before your handler ever runs. JSON Schema is a declarative format for describing the shape and constraints of JSON data.
Declare a schema and let Fastify enforce it
This example assumes Fastify v4 or later. Create a file named server.js:
const fastify = require('fastify')({ logger: true })
fastify.post('/users', {
schema: {
body: {
type: 'object',
required: ['email'],
properties: {
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0 }
}
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'string' },
email: { type: 'string' }
}
}
}
}
}, async (request, reply) => {
return { id: 'user-123', email: request.body.email }
})
fastify.listen({ port: 3000 }, (err) => {
if (err) throw err
})
Run it from your project directory. No special permissions are required:
$ node server.js
In another terminal, send a valid payload:
$ curl -X POST http://localhost:3000/users \
-H 'Content-Type: application/json' \
-d '{"email": "[contact removed]", "age": 30}'
Then send an invalid one:
$ curl -X POST http://localhost:3000/users \
-H 'Content-Type: application/json' \
-d '{"email": "not-an-email"}'
Expected check: the invalid request should return a 400 Bad Request, and your handler should not execute. If you see a 200 or a handler error instead, the schema may not be registered or the request content type may not be application/json.
Risk: a malformed schema can cause Fastify to fail at startup, so validate your schema definitions during development.
Compile serializers with fast-json-stringify
Validation protects the input side. For the output side, fast-json-stringify compiles a schema into a dedicated serialization function. Unlike JSON.stringify, which inspects every object at runtime, the compiled function knows the shape ahead of time and skips redundant property checks. This matters most on high-throughput endpoints that return large or repeated payloads.
const fastJson = require('fast-json-stringify')
const stringifyUser = fastJson({
type: 'object',
properties: {
id: { type: 'string' },
email: { type: 'string' },
createdAt: { type: 'string' }
}
})
const payload = {
id: 'user-123',
email: '[contact removed]',
createdAt: '2026-11-13T06:34:42Z'
}
console.log(stringifyUser(payload))
Use this when the response shape is stable. If your endpoint returns highly dynamic or arbitrary objects, the compiled serializer offers less benefit and may add complexity.
TypeScript: types without duplicating schemas
If you use TypeScript, fastify-type-provider packages let you write the schema once and infer route parameter, body, and response types. With @fastify/type-provider-typebox, for example:
import fastify from 'fastify'
import { TypeProviderTypebox, Type } from '@fastify/type-provider-typebox'
const app = fastify().withTypeProvider()
app.post('/users', {
schema: {
body: Type.Object({
email: Type.String({ format: 'email' }),
age: Type.Optional(Type.Integer({ minimum: 0 }))
}),
response: {
200: Type.Object({
id: Type.String(),
email: Type.String()
})
}
}
}, async (request, reply) => {
// request.body.email is typed as string
return { id: 'user-123', email: request.body.email }
})
await app.listen({ port: 3000 })
Install the provider package with npm before importing it. Package names and APIs can change between Fastify major versions, so check the provider documentation for your installed version.
Trade-offs and limits
- Schema validation adds a small parse-time cost, but it usually saves more time than it costs by preventing handler execution on bad input.
fast-json-stringifyworks best with stable response shapes. Dynamic payloads may not see a meaningful improvement.- Schema behavior can change between Fastify major versions. Review migration notes when upgrading.
Verify before you deploy
- Start the server and send a payload that violates the schema. Confirm you receive a 400 before any handler logic runs.
- Run a local benchmark comparing
fast-json-stringifyagainstJSON.stringifyon identical payloads usingconsole.timeor a benchmarking tool. Do not assume the improvement applies to your workload without measuring it. - Inspect registered schemas with Fastify's built-in introspection if a validation rule is not behaving as expected.
Start with one endpoint. Add a schema, confirm the 400 response on bad input, then add a compiled serializer for the response. Once the pattern is proven, extend it to the rest of your API.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.