Free & open source — no account required

v1.2.5-PRICING-19
Web & Frontend • Engineering Documentation

JSON to Fastify Schema Tool

This technical guide provides an in-depth analysis of the json to fastify engine, best practices for implementation, and data security standards.

JSON to Fastify Schema: Type-Safe Routes with JSON Schema Validation

Fastify validates request bodies, query strings, path parameters, and headers using JSON Schema at the framework level — before your handler runs. This makes invalid data impossible to reach your business logic. But Fastify's default TypeScript experience is loose: without a type provider, request.body is unknown. Generating TypeBox or Zod schemas from your JSON gives you both runtime validation and compile-time types in one step.

Fastify's Type Provider System

Fastify 4+ uses type providers to connect schema definitions to TypeScript types. The two official providers are TypeBox and Zod:

// TypeBox provider — schemas are JSON Schema objects, type inference is automatic
import Fastify from 'fastify';
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';

const app = Fastify().withTypeProvider<TypeBoxTypeProvider>();

// Zod provider — schemas are Zod objects, converted to JSON Schema internally
import { ZodTypeProvider } from 'fastify-type-provider-zod';

const app = Fastify().withTypeProvider<ZodTypeProvider>();

Typed Route with TypeBox

import Fastify from 'fastify';
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import { Type, Static } from '@sinclair/typebox';

const app = Fastify({ logger: true }).withTypeProvider<TypeBoxTypeProvider>();

// Define schemas from your JSON sample
const CreateUserBody = Type.Object({
  name:     Type.String({ minLength: 2, maxLength: 50 }),
  email:    Type.String({ format: 'email' }),
  role:     Type.Union([
    Type.Literal('admin'),
    Type.Literal('user'),
    Type.Literal('guest'),
  ]),
  metadata: Type.Optional(Type.Object({
    source:   Type.String(),
    referral: Type.Optional(Type.String()),
  })),
});

const UserResponse = Type.Object({
  id:         Type.String({ format: 'uuid' }),
  name:       Type.String(),
  email:      Type.String(),
  role:       Type.String(),
  created_at: Type.String({ format: 'date-time' }),
});

const ErrorResponse = Type.Object({
  statusCode: Type.Number(),
  message:    Type.String(),
});

app.post('/users', {
  schema: {
    body:     CreateUserBody,
    response: {
      201: UserResponse,
      400: ErrorResponse,
      409: ErrorResponse,
    },
  },
}, async (request, reply) => {
  // ✅ request.body is typed as Static<typeof CreateUserBody>
  // TypeScript knows: name is string, email is string, role is 'admin'|'user'|'guest'
  const { name, email, role, metadata } = request.body;

  const existing = await db.users.findByEmail(email);
  if (existing) return reply.status(409).send({ statusCode: 409, message: 'Email already exists' });

  const user = await db.users.create({ name, email, role });
  return reply.status(201).send(user);
});

Query String and Path Parameter Types

const ListUsersQuery = Type.Object({
  page:   Type.Optional(Type.Integer({ minimum: 1, default: 1 })),
  limit:  Type.Optional(Type.Integer({ minimum: 1, maximum: 100, default: 20 })),
  role:   Type.Optional(Type.Union([
    Type.Literal('admin'), Type.Literal('user'), Type.Literal('guest')
  ])),
  search: Type.Optional(Type.String({ minLength: 1 })),
});

const UserParams = Type.Object({
  id: Type.String({ format: 'uuid' }),
});

app.get('/users/:id', {
  schema: {
    params:      UserParams,
    querystring: ListUsersQuery,
  },
}, async (request, reply) => {
  // request.params.id is typed as string
  // request.query.page is typed as number | undefined
  const { id }            = request.params;
  const { page = 1, limit = 20 } = request.query;

  const user = await db.users.findById(id);
  if (!user) return reply.status(404).send({ statusCode: 404, message: 'User not found' });

  return reply.send(user);
});

Typed Route with Zod Provider

import Fastify from 'fastify';
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from 'fastify-type-provider-zod';
import { z } from 'zod';

const app = Fastify().withTypeProvider<ZodTypeProvider>();
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);

const CreateOrderSchema = z.object({
  customer_id: z.string().uuid(),
  items: z.array(z.object({
    product_id: z.string(),
    quantity:   z.number().int().positive(),
  })).min(1),
  coupon_code: z.string().optional(),
});

app.post('/orders', {
  schema: { body: CreateOrderSchema },
}, async (request, reply) => {
  // request.body is typed as z.infer<typeof CreateOrderSchema> ✅
  const { customer_id, items, coupon_code } = request.body;
  // ...
});

Plugin Registration with Types

Fastify plugins need explicit type declarations to propagate types across the instance:

import fp from 'fastify-plugin';
import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox';

// Type-safe plugin
const usersPlugin: FastifyPluginAsyncTypebox = async (app) => {
  app.get('/users', {
    schema: {
      querystring: Type.Object({
        page: Type.Optional(Type.Integer({ minimum: 1, default: 1 })),
      }),
    },
  }, async (request) => {
    const { page } = request.query; // typed as number | undefined ✅
    return db.users.list({ page });
  });
};

export default fp(usersPlugin);

Response Schema for Serialization Speed

Response schemas in Fastify don't just add type safety — they also make serialization ~2-3x faster by avoiding JSON.stringify's reflection overhead:

// Without response schema: Fastify uses JSON.stringify (slow for large objects)
// With response schema:    Fastify uses fast-json-stringify (generated serializer)

const BigDataResponse = Type.Object({
  total:  Type.Integer(),
  items:  Type.Array(Type.Object({
    id:    Type.String(),
    value: Type.Number(),
    tags:  Type.Array(Type.String()),
  })),
});

// Defining the response schema alone gives a performance boost,
// even if you don't need the TypeScript type inference.
app.get('/data', {
  schema: { response: { 200: BigDataResponse } },
}, async () => db.bigData.list());

Common Pitfalls

  • Forgetting .withTypeProvider(): Without the type provider, all route handler parameters type as unknown even when schemas are defined. Always attach the provider to the Fastify instance before defining routes.
  • Query strings are always strings from the network: Fastify's JSON Schema can coerce query params ("type": "integer" parses "5" to 5), but this only works if you define the schema. Without a schema, query params arrive as raw strings.
  • Serialization vs. validation schemas: The response schema is used for serialization (not validation). It will strip fields not present in the schema from the output. This is a feature, not a bug — but unexpected if you're returning extra fields from the database.
  • Shared schemas and $ref: Fastify supports shared JSON Schemas via app.addSchema and $ref. TypeBox and the Zod provider handle this differently — check the provider's docs before using shared schemas to avoid type inference breaking.

FAQ

Q: TypeBox or Zod with Fastify — which should I choose?
A: TypeBox is the native choice for Fastify — zero overhead since it's already JSON Schema. Zod is better if you want the same schema library across Fastify and your other validation code (forms, CLI parsing, etc.). Performance difference is negligible for most APIs.

Q: How do I handle file uploads with type-safe schemas?
A: File uploads use multipart, not JSON. Use @fastify/multipart to handle the body and validate the non-file fields separately. The file itself is a stream — type it as MultipartFile from the plugin.

Q: Can I generate OpenAPI documentation from Fastify route schemas?
A: Yes — @fastify/swagger reads all registered route schemas and generates an OpenAPI 3 spec automatically. Add @fastify/swagger-ui to serve the interactive docs at /documentation.

Q: How do I validate headers in Fastify?
A: Add a headers property to the route schema: schema: { headers: Type.Object({ 'x-api-key': Type.String() }) }. Note that HTTP header names are case-insensitive — use lowercase keys in the schema.

Developer FAQ

Is the processing local-only?

Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.

Can I use this for enterprise projects?

Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.