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 🙏

© 2024 – Pkg Stats / Ryan Hefner

fastify-zod-openapi

v4.0.0

Published

Fastify plugin for zod-openapi

Downloads

48,604

Readme

Install

Install via npm, pnpm or pnpm:

npm install zod zod-openapi fastify-zod-openapi
## or
pnpm add zod zod-openapi fastify-zod-openapi
## or
pnpm install zod-openapi fastify-zod-openapi

Usage

import 'zod-openapi/extend';
import fastify from 'fastify';
import {
  type FastifyZodOpenApiSchema,
  type FastifyZodOpenApiTypeProvider,
  serializerCompiler,
  validatorCompiler,
} from 'fastify-zod-openapi';
import { z } from 'zod';

const app = fastify();

app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);

app.withTypeProvider<FastifyZodOpenApiTypeProvider>().route({
  method: 'POST',
  url: '/:jobId',
  schema: {
    body: z.object({
      jobId: z.string().openapi({
        description: 'Job ID',
        example: '60002023',
      }),
    }),
    response: {
      201: z.object({
        jobId: z.string().openapi({
          description: 'Job ID',
          example: '60002023',
        }),
      }),
    },
  } satisfies FastifyZodOpenApiSchema,
  handler: async (req, res) => {
    await res.send({ jobId: req.body.jobId });
  },
});

await app.ready();
await app.listen({ port: 5000 });

Usage with plugins

import 'zod-openapi/extend';
import { FastifyPluginAsyncZodOpenApi } from 'fastify-zod-openapi';
import { z } from 'zod';

const plugin: FastifyPluginAsyncZodOpenApi = async (fastify, _opts) => {
  fastify.route({
    method: 'POST',
    url: '/',
    // Define your schema
    schema: {
      body: z.object({
        jobId: z.string().openapi({
          description: 'Job ID',
          example: '60002023',
        }),
      }),
      response: {
        201: z.object({
          jobId: z.string().openapi({
            description: 'Job ID',
            example: '60002023',
          }),
        }),
      },
    } satisfies FastifyZodOpenApiSchema,
    handler: async (req, res) => {
      await res.send({ jobId: req.body.jobId });
    },
  });
};

app.register(plugin);

Usage with @fastify/swagger

import 'zod-openapi/extend';
import fastifySwagger from '@fastify/swagger';
import fastifySwaggerUI from '@fastify/swagger-ui';
import fastify from 'fastify';
import {
  type FastifyZodOpenApiSchema,
  type FastifyZodOpenApiTypeProvider,
  fastifyZodOpenApiPlugin,
  fastifyZodOpenApiTransform,
  fastifyZodOpenApiTransformObject,
  serializerCompiler,
  validatorCompiler,
} from 'fastify-zod-openapi';
import { z } from 'zod';
import { type ZodOpenApiVersion } from 'zod-openapi';

const app = fastify();

app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);

await app.register(fastifyZodOpenApiPlugin);
await app.register(fastifySwagger, {
  openapi: {
    info: {
      title: 'hello world',
      version: '1.0.0',
    },
    openapi: '3.0.3' satisfies ZodOpenApiVersion, // If this is not specified, it will default to 3.1.0
  },
  transform: fastifyZodOpenApiTransform,
  transformObject: fastifyZodOpenApiTransformObject,
});
await app.register(fastifySwaggerUI, {
  routePrefix: '/documentation',
});

app.withTypeProvider<FastifyZodOpenApiTypeProvider>().route({
  method: 'POST',
  url: '/',
  schema: {
    body: z.string().openapi({
      description: 'Job ID',
      example: '60002023',
    }),
    response: {
      201: {
        content: {
          'application/json': {
            schema: z.object({
              jobId: z.string().openapi({
                description: 'Job ID',
                example: '60002023',
              }),
            }),
          },
        },
      },
    },
  } satisfies FastifyZodOpenApiSchema,
  handler: async (_req, res) =>
    res.send({
      jobId: '60002023',
    }),
});
await app.ready();
await app.listen({ port: 5000 });

Declaring Components

This library allows you to easily declare components. As an example:

const title = z.string().openapi({
  description: 'Job title',
  example: 'My job',
  ref: 'jobTitle', // <- new field
});

Wherever title is used in your request/response schemas across your application, it will instead be created as a reference.

{ "$ref": "#/components/schemas/jobTitle" }

For a further dive please follow the documentation here.

If you wish to declare the components manually you will need to do so via the plugin's options. You will also need to create a custom SerializerCompiler to make use of fast-json-stringify.

const components: ZodOpenApiComponentsObject = { schemas: { mySchema } };
await app.register(fastifyZodOpenApiPlugin, {
  components,
});

const customSerializerCompiler = createSerializerCompiler({
  components,
});

Alternatively, you can use JSON.stringify instead.

const customSerializerCompiler = createSerializerCompiler({
  stringify: JSON.stringify,
});

Please note: the responses, parameters components do not appear to be supported by the @fastify/swagger library.

Create Document Options

If you wish to use CreateDocumentOptions, pass it in via the plugin options:

await app.register(fastifyZodOpenApiPlugin, {
  documentOpts: {
    unionOneOf: true,
  },
});

Custom Response Serializer

The default response serializer serializerCompiler uses fast-json-stringify. Under the hood, the schema passed to the response is transformed using OpenAPI 3.1.0 and passed to fast-json-stringify as a JSON Schema.

If are running into any compatibility issues or wish to restore the previous JSON.stringify functionality, you can use the createSerializerCompiler function.

const customSerializerCompiler = createSerializerCompiler({
  stringify: JSON.stringify,
});

Error Handling

By default, fastify-zod-openapi emits request validation errors in a similar manner to fastify when used in conjunction with it's native JSON Schema error handling.

As an example:

{
  "code": "FST_ERR_VALIDATION",
  "error": "Bad Request",
  "message": "params/jobId Expected number, received string",
  "statusCode": 400
}

For responses, it will emit a 500 error along with a vague error which will protect your implementation details

{
  "code": "FST_ERR_RESPONSE_SERIALIZATION",
  "error": "Internal Server Error",
  "message": "Response does not match the schema",
  "statusCode": 500
}

To customise this behaviour, you may follow the fastify error handling guidance.

Request Errors

This library throws a RequestValidationError when a request fails to validate against your Zod Schemas

setErrorHandler
fastify.setErrorHandler(function (error, request, reply) {
  if (error.validation) {
    const zodValidationErrors = error.validation.filter(
      (err) => err instanceof RequestValidationError,
    );
    const zodIssues = zodValidationErrors.map((err) => err.params.issue);
    const originalError = zodValidationErrors?.[0]?.params.error;
    return reply.status(422).send({
      zodIssues
      originalError
    });
  }
});
setSchemaErrorFormatter
fastify.setSchemaErrorFormatter(function (errors, dataVar) {
  let message = `${dataVar}:`;
  for (const error of errors) {
    if (error instanceof RequestValidationError) {
      message += ` ${error.instancePath} ${error.keyword}`;
    }
  }

  return new Error(message);
});

// {
// code: 'FST_ERR_VALIDATION',
// error: 'Bad Request',
// message: 'querystring: /jobId invalid_type',
// statusCode: 400,
// }
attachValidation
app.withTypeProvider<FastifyZodOpenApiTypeProvider>().get(
  '/',
  {
    schema: {
      querystring: z.object({
        jobId: z.string().openapi({
          description: 'Job ID',
          example: '60002023',
        }),
      }),
    },
    attachValidation: true,
  },
  (req, res) => {
    if (req.validationError?.validation) {
      const zodValidationErrors = req.validationError.validation.filter(
        (err) => err instanceof RequestValidationError,
      );
      console.error(zodValidationErrors);
    }

    return res.send(req.query);
  },
);

Response Errors

app.setErrorHandler((error, _req, res) => {
  if (error instanceof ResponseSerializationError) {
    return res.status(500).send({
      error: 'Bad response',
    });
  }
});

// {
//   error: 'Bad response';
// }

Credits

fastify-type-provider-zod: Big kudos to this library for lighting the way with how to create type providers, validators and serializers. fastify-zod-openapi is just an extension to this library whilst adding support for the functionality of zod-openapi.

Development

Prerequisites

  • Node.js LTS
  • pnpm
pnpm install
pnpm build

Test

pnpm test

Lint

# Fix issues
pnpm format

# Check for issues
pnpm lint

Release

To release a new version

  1. Create a new GitHub Release
  2. Select 🏷️ Choose a tag, enter a version number. eg. v1.2.0 and click + Create new tag: vX.X.X on publish.
  3. Click the Generate release notes button and adjust the description.
  4. Tick the Set as the latest release box and click Publish release. This will trigger the Release workflow.
  5. Check the Pull Requests tab for a PR labelled Release vX.X.X.
  6. Click Merge Pull Request on that Pull Request to update main with the new package version.

To release a new beta version

  1. Create a new GitHub Release
  2. Select 🏷️ Choose a tag, enter a version number with a -beta.X suffix eg. v1.2.0-beta.1 and click + Create new tag: vX.X.X-beta.X on publish.
  3. Click the Generate release notes button and adjust the description.
  4. Tick the Set as a pre-release box and click Publish release. This will trigger the Prerelease workflow.