npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fastify-lor-zod

v0.8.0

Published

A Fastify type provider integrating Zod v4 for schema validation, response serialization, and OpenAPI generation

Downloads

325

Readme

fastify-lor-zod

CI npm version license TypeScript

Note -- Pre-1.0: minor versions may include breaking changes. Pin your version and check the changelog before upgrading.

A Fastify type provider for Zod v4 with full OpenAPI support. A ground-up rebuild of turkerdev/fastify-type-provider-zod on Zod v4's native APIs — fixes 25+ open issues.

Why fastify-lor-zod?

  • Zod v4 native -- uses safeEncode, toJSONSchema, codecs, and registries directly
  • Smart serializer -- auto-detects codecs at compile time; falls back to safeParse for ~15% faster non-codec schemas
  • Complete OpenAPI -- all HTTP parts, nullable types, discriminated unions, recursive schemas, content types
  • Type-safe end-to-end -- req.body, req.params, req.query, req.headers, and reply.send() fully typed
  • 100% test coverage with snapshot parity against fastify-type-provider-zod
  • Why "Lor"? -- Son of Zod, here to power your fastify schemas.

Table of Contents

Install

pnpm add fastify-lor-zod
pnpm add -D fastify zod                    # peer dependencies
pnpm add -D @fastify/swagger               # optional, for OpenAPI

Quick Start

import Fastify from 'fastify';
import { z } from 'zod';
import {
  validatorCompiler,
  serializerCompiler,
  type FastifyLorZodTypeProvider,
} from 'fastify-lor-zod';

const app = Fastify();
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);

app.withTypeProvider<FastifyLorZodTypeProvider>().get(
  '/user/:id',
  {
    schema: {
      params: z.object({ id: z.coerce.number() }),
      response: {
        200: z.object({ id: z.number(), name: z.string() }),
      },
    },
  },
  (req) => ({ id: req.params.id, name: 'Alice' }),
  //              ^ fully typed as number
);

app.listen({ port: 3000 });

Serializer Compilers

Three strategies for different trade-offs:

| Compiler | Validates | Codecs | Speed | Use when | | -------- | --------- | ------ | ----- | -------- | | serializerCompiler | Yes | Auto-detect | Fastest validating | Recommended default -- uses safeParse for plain schemas, safeEncode only when codecs are present | | parseSerializerCompiler | Yes | No | Same as above | Explicit opt-in to always use safeParse | | fastSerializerCompiler | No | No | Fastest overall | You trust your handlers and want maximum throughput |

import {
  serializerCompiler,         // default: auto-detects codecs, picks safeParse or safeEncode
  parseSerializerCompiler,    // always z.safeParse + JSON.stringify
  fastSerializerCompiler,     // fast-json-stringify, no validation
} from 'fastify-lor-zod';

app.setSerializerCompiler(serializerCompiler);

createSerializerCompiler and createParseSerializerCompiler each accept a replacer option for JSON.stringify. createFastSerializerCompiler takes no options — fast-json-stringify pre-compiles the serializer at route registration time and does not use JSON.stringify.

Benchmarks

Serialization throughput (ops/sec, higher is better):

| Scenario | lor-zod | lor-zod (parse) | lor-zod (fast) | type-provider-zod | zod-openapi | | -------- | ------- | --------------- | -------------- | ----------------- | ----------- | | Simple object | 278K | 287K | 610K | 291K | 271K | | Simple object + date codec | 142K | Unsupported | 211K | Unsupported | Unsupported | | Nested (10 items) | 33K | 34K | 86K | 34K | 30K | | Nested + money codec | 29K | Unsupported | 90K | Unsupported | Unsupported | | Discriminated union | 499K | 487K | 651K | 505K | 316K | | Recursive tree | 407K | 383K | 1.13M | 397K | 438K |

For non-codec schemas, serializerCompiler auto-detects and matches parseSerializerCompiler speed. For codec schemas, it automatically uses safeEncode.

Validation throughput (all libraries are within ~5% of each other):

| Scenario | lor-zod | type-provider-zod | zod-openapi | | -------- | ------- | ----------------- | ----------- | | Simple object | 386K | 360K | 366K | | Nested (10 items) | 57K | 57K | 58K | | Discriminated union | 996K | 946K | 933K | | Recursive tree | 819K | 805K | 758K |

Measured on Apple M-series, Node.js 24, Zod 4.3.6. Run pnpm bench to reproduce, or pnpm bench:lib lor-zod for this library only.

OpenAPI / Swagger

Integrate with @fastify/swagger for automatic OpenAPI spec generation. transform converts Zod schemas per route, transformObject populates components.schemas from a registry (safe to include even without one):

import swagger from '@fastify/swagger';
import swaggerUi from '@fastify/swagger-ui';
import { jsonSchemaTransform, jsonSchemaTransformObject } from 'fastify-lor-zod';

await app.register(swagger, {
  openapi: {
    openapi: '3.0.3',
    info: { title: 'My API', version: '1.0.0' },
  },
  transform: jsonSchemaTransform,
  transformObject: jsonSchemaTransformObject,
});

await app.register(swaggerUi, { routePrefix: '/documentation' });
  • OAS 3.0 and 3.1 support
  • Automatic io: "input" for request schemas, io: "output" for response schemas
  • Nullable types, discriminated unions, recursive schemas handled correctly
  • Nested content types (application/json, multipart/form-data, etc.)
  • Response description preserved from wrapper objects
  • zodToJsonConfig passthrough for custom z.toJSONSchema() options

Schema Registry

Register schemas with z.globalRegistry or a custom registry to generate $ref-based components.schemas:

import { z } from 'zod';
import { createJsonSchemaTransforms } from 'fastify-lor-zod';

const registry = z.registry<{ id: string }>();
const UserSchema = z.object({ id: z.number(), name: z.string() });
registry.add(UserSchema, { id: 'User' });

await app.register(swagger, {
  openapi: { openapi: '3.0.3', info: { title: 'My API', version: '1.0.0' } },
  ...createJsonSchemaTransforms({ schemaRegistry: registry }),
});

Schemas whose input and output shapes diverge (e.g. due to .default(), transforms, or codecs) automatically get {Id}Input variants in components.schemas. No configuration needed.

Typed Plugins

import type { FastifyPluginAsyncZod } from 'fastify-lor-zod';

const usersPlugin: FastifyPluginAsyncZod = async (app) => {
  app.get(
    '/users',
    {
      schema: {
        response: { 200: z.array(UserSchema) },
      },
    },
    () => [{ id: 1, name: 'Alice' }],
  );
};

await app.register(usersPlugin);

Typed Handlers

Use RouteHandler to define handlers in separate files while preserving Zod type inference:

import type { RouteHandler } from 'fastify-lor-zod';

const schema = {
  params: z.object({ id: z.coerce.number() }),
  response: { 200: z.object({ name: z.string() }) },
} as const;

const getUser: RouteHandler<typeof schema> = (req) => {
  req.params.id; // number
  return { name: 'Alice' };
};

app.get('/users/:id', { schema }, getUser);

Error Handling

Validation errors are detected with the isRequestValidationError type guard. Serialization errors use instanceof on the ResponseSerializationError class.

import {
  isRequestValidationError,
  ResponseSerializationError,
} from 'fastify-lor-zod';

app.setErrorHandler((error, request, reply) => {
  if (isRequestValidationError(error)) {
    // Log input server-side only — may contain sensitive fields
    request.log.error({ input: error.input });
    reply.code(400).send({
      error: 'Validation failed',
      issues: error.validation,          // FastifySchemaValidationError[]
      context: error.validationContext,   // 'body' | 'querystring' | 'params' | 'headers'
    });
    return;
  }

  if (error instanceof ResponseSerializationError) {
    reply.code(500).send({
      error: 'Response serialization failed',
      code: error.code,        // 'ERR_RESPONSE_SERIALIZATION'
      method: error.method,    // 'GET'
      url: error.url,          // '/users/42'
      httpStatus: error.httpStatus, // '200'
    });
    return;
  }

  reply.send(error);
});

Zod v4 Codec Support

Zod v4 codecs encode domain types to wire format. The default serializer handles this automatically:

const dateCodec = z.codec(z.iso.datetime(), z.date(), {
  decode: (iso: string) => new Date(iso),
  encode: (date: Date) => date.toISOString(),
});

app.get(
  '/event',
  {
    schema: {
      response: {
        200: z.object({ startsAt: dateCodec }),
      },
    },
  },
  () => ({ startsAt: new Date() }),
  // Response: { "startsAt": "2025-06-15T10:00:00.000Z" }
);

Compatibility

| fastify-lor-zod | Fastify | Zod | @fastify/swagger | fast-json-stringify | Node.js | | --------------- | ------- | --- | ---------------- | ------------------- | ------- | | 0.x | >= 5.8.4 | >= 4.3.6 | >= 9.7.0 (optional) | >= 6.3.0 (optional, for fastSerializerCompiler) | >= 22 |

Migrating from fastify-type-provider-zod

See MIGRATION.md for a step-by-step guide.

Contributing

git clone https://github.com/drudolf/fastify-lor-zod.git
cd fastify-lor-zod
pnpm install

| Command | Description | | ------- | ----------- | | pnpm test | Run tests | | pnpm test:coverage | Run tests with 100% coverage enforcement | | pnpm check | Lint + format (Biome) | | pnpm typecheck | Type-check with tsc --noEmit | | pnpm knip | Detect unused exports and dependencies | | pnpm bench | Run benchmarks against all type providers | | pnpm bench:lib <filter> | Run benchmarks for a single library (e.g. lor-zod, type-provider, zod-openapi) | | pnpm build | Build the project (ESM and CJS) |

Tests follow a spec-first workflow -- see test-spec.md for the full test matrix and CLAUDE.md for project conventions.

License

MIT