{"id":16650556,"url":"https://github.com/samchungy/fastify-zod-openapi","last_synced_at":"2025-04-12T20:42:34.279Z","repository":{"id":178035027,"uuid":"661276221","full_name":"samchungy/fastify-zod-openapi","owner":"samchungy","description":"Fastify plugin for zod-openapi","archived":false,"fork":false,"pushed_at":"2025-03-31T07:08:44.000Z","size":1940,"stargazers_count":74,"open_issues_count":6,"forks_count":11,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-05T11:36:02.177Z","etag":null,"topics":["fastify","fastify-plugin","openapi","typescript","zod"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samchungy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["samchungy"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2023-07-02T10:45:56.000Z","updated_at":"2025-04-01T02:05:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"fff2b44d-1c31-48bf-8412-50d77a736bc9","html_url":"https://github.com/samchungy/fastify-zod-openapi","commit_stats":null,"previous_names":["samchungy/fastify-zod-openapi"],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samchungy%2Ffastify-zod-openapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samchungy%2Ffastify-zod-openapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samchungy%2Ffastify-zod-openapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samchungy%2Ffastify-zod-openapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samchungy","download_url":"https://codeload.github.com/samchungy/fastify-zod-openapi/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248631669,"owners_count":21136554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fastify","fastify-plugin","openapi","typescript","zod"],"created_at":"2024-10-12T09:16:59.803Z","updated_at":"2025-04-12T20:42:34.245Z","avatar_url":"https://github.com/samchungy.png","language":"TypeScript","funding_links":["https://github.com/sponsors/samchungy"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\"\u003efastify-zod-openapi\u003c/h1\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\nFastify \u003ca href=\"https://fastify.dev/docs/latest/Reference/Type-Providers/\"\u003etype provider\u003c/a\u003e, \u003ca href=\"https://fastify.dev/docs/latest/Reference/Validation-and-Serialization/\"\u003evalidation, serialization\u003c/a\u003e and \u003ca href=\"https://github.com/fastify/fastify-swagger\"\u003e@fastify/swagger\u003c/a\u003e support for \u003ca href=\"https://github.com/samchungy/zod-openapi\"\u003ezod-openapi\u003c/a\u003e.\n\u003c/p\u003e\n\u003cdiv align=\"center\"\u003e\n\u003ca href=\"https://www.npmjs.com/package/fastify-zod-openapi\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/fastify-zod-openapi\"/\u003e\u003ca\u003e\n\u003ca href=\"https://www.npmjs.com/package/fastify-zod-openapi\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/fastify-zod-openapi\"/\u003e\u003ca\u003e\n\u003ca href=\"https://nodejs.org/en/\"\u003e\u003cimg src=\"https://img.shields.io/badge/node-%3E%3D%2020-brightgreen\"/\u003e\u003ca\u003e\n\u003ca href=\"https://github.com/samchungy/fastify-zod-openapi/actions/workflows/test.yml\"\u003e\u003cimg src=\"https://github.com/samchungy/fastify-zod-openapi/actions/workflows/test.yml/badge.svg\"/\u003e\u003ca\u003e\n\u003ca href=\"https://github.com/samchungy/fastify-zod-openapi/actions/workflows/release.yml\"\u003e\u003cimg src=\"https://github.com/samchungy/fastify-zod-openapi/actions/workflows/release.yml/badge.svg\"/\u003e\u003ca\u003e\n\u003ca href=\"https://github.com/seek-oss/skuba\"\u003e\u003cimg src=\"https://img.shields.io/badge/🤿%20skuba-powered-009DC4\"/\u003e\u003ca\u003e\n\u003c/div\u003e\n\u003cbr\u003e\n\n## Install\n\nInstall via `npm`, `pnpm` or `pnpm`:\n\n```bash\nnpm install zod zod-openapi fastify-zod-openapi\n## or\npnpm add zod zod-openapi fastify-zod-openapi\n## or\npnpm install zod-openapi fastify-zod-openapi\n```\n\n## Usage\n\n```ts\nimport 'zod-openapi/extend';\nimport fastify from 'fastify';\nimport {\n  type FastifyZodOpenApiSchema,\n  type FastifyZodOpenApiTypeProvider,\n  serializerCompiler,\n  validatorCompiler,\n} from 'fastify-zod-openapi';\nimport { z } from 'zod';\n\nconst app = fastify();\n\napp.setValidatorCompiler(validatorCompiler);\napp.setSerializerCompiler(serializerCompiler);\n\napp.withTypeProvider\u003cFastifyZodOpenApiTypeProvider\u003e().route({\n  method: 'POST',\n  url: '/:jobId',\n  schema: {\n    body: z.object({\n      jobId: z.string().openapi({\n        description: 'Job ID',\n        example: '60002023',\n      }),\n    }),\n    response: {\n      201: z.object({\n        jobId: z.string().openapi({\n          description: 'Job ID',\n          example: '60002023',\n        }),\n      }),\n    },\n  } satisfies FastifyZodOpenApiSchema,\n  handler: async (req, res) =\u003e {\n    await res.send({ jobId: req.body.jobId });\n  },\n});\n\nawait app.ready();\nawait app.listen({ port: 5000 });\n```\n\n## Usage with plugins\n\n```ts\nimport 'zod-openapi/extend';\nimport { FastifyPluginAsyncZodOpenApi } from 'fastify-zod-openapi';\nimport { z } from 'zod';\n\nconst plugin: FastifyPluginAsyncZodOpenApi = async (fastify, _opts) =\u003e {\n  fastify.route({\n    method: 'POST',\n    url: '/',\n    // Define your schema\n    schema: {\n      body: z.object({\n        jobId: z.string().openapi({\n          description: 'Job ID',\n          example: '60002023',\n        }),\n      }),\n      response: {\n        201: z.object({\n          jobId: z.string().openapi({\n            description: 'Job ID',\n            example: '60002023',\n          }),\n        }),\n      },\n    } satisfies FastifyZodOpenApiSchema,\n    handler: async (req, res) =\u003e {\n      await res.send({ jobId: req.body.jobId });\n    },\n  });\n};\n\napp.register(plugin);\n```\n\n## Usage with @fastify/swagger\n\n```ts\nimport 'zod-openapi/extend';\nimport fastifySwagger from '@fastify/swagger';\nimport fastifySwaggerUI from '@fastify/swagger-ui';\nimport fastify from 'fastify';\nimport {\n  type FastifyZodOpenApiSchema,\n  type FastifyZodOpenApiTypeProvider,\n  fastifyZodOpenApiPlugin,\n  fastifyZodOpenApiTransform,\n  fastifyZodOpenApiTransformObject,\n  serializerCompiler,\n  validatorCompiler,\n} from 'fastify-zod-openapi';\nimport { z } from 'zod';\nimport { type ZodOpenApiVersion } from 'zod-openapi';\n\nconst app = fastify();\n\napp.setValidatorCompiler(validatorCompiler);\napp.setSerializerCompiler(serializerCompiler);\n\nawait app.register(fastifyZodOpenApiPlugin);\nawait app.register(fastifySwagger, {\n  openapi: {\n    info: {\n      title: 'hello world',\n      version: '1.0.0',\n    },\n    openapi: '3.0.3' satisfies ZodOpenApiVersion, // If this is not specified, it will default to 3.1.0\n  },\n  transform: fastifyZodOpenApiTransform,\n  transformObject: fastifyZodOpenApiTransformObject,\n});\nawait app.register(fastifySwaggerUI, {\n  routePrefix: '/documentation',\n});\n\napp.withTypeProvider\u003cFastifyZodOpenApiTypeProvider\u003e().route({\n  method: 'POST',\n  url: '/',\n  schema: {\n    body: z.object({\n      jobId: z.string().openapi({\n        description: 'Job ID',\n        example: '60002023',\n      }),\n    }),\n    response: {\n      201: {\n        content: {\n          'application/json': {\n            schema: z.object({\n              jobId: z.string().openapi({\n                description: 'Job ID',\n                example: '60002023',\n              }),\n            }),\n          },\n        },\n      },\n    },\n  } satisfies FastifyZodOpenApiSchema,\n  handler: async (_req, res) =\u003e\n    res.send({\n      jobId: '60002023',\n    }),\n});\nawait app.ready();\nawait app.listen({ port: 5000 });\n```\n\n### Declaring Components\n\nThis library allows you to easily declare components. As an example:\n\n```typescript\nconst title = z.string().openapi({\n  description: 'Job title',\n  example: 'My job',\n  ref: 'jobTitle', // \u003c- new field\n});\n```\n\nWherever `title` is used in your request/response schemas across your application, it will instead be created as a reference.\n\n```json\n{ \"$ref\": \"#/components/schemas/jobTitle\" }\n```\n\nFor a further dive please follow the documentation [here](https://github.com/samchungy/zod-openapi#creating-components).\n\nIf you wish to declare the components manually you will need to do so via the plugin's options. You will also need\nto create a custom SerializerCompiler to make use of [fast-json-stringify](https://github.com/fastify/fast-json-stringify).\n\n```ts\nconst components: ZodOpenApiComponentsObject = { schemas: { mySchema } };\nawait app.register(fastifyZodOpenApiPlugin, {\n  components,\n});\n\nconst customSerializerCompiler = createSerializerCompiler({\n  components,\n});\n```\n\nAlternatively, you can use `JSON.stringify` instead.\n\n```ts\nconst customSerializerCompiler = createSerializerCompiler({\n  stringify: JSON.stringify,\n});\n```\n\nBy default, this library assumes that if a response schema provided is not a Zod Schema, it is a JSON Schema and will naively pass it straight into `fast-json-stringify`. This will not work in conjunction with Fastify's schema registration.\n\nIf you have other routes with response schemas which are not Zod Schemas, you can supply a `fallbackSerializer` to `createSerializerCompiler`.\n\n```ts\nconst customSerializerCompiler = createSerializerCompiler({\n  fallbackSerializer: ({ schema, url, method }) =\u003e customSerializer(schema),\n});\n```\n\nPlease note: the `responses`, `parameters` components do not appear to be supported by the `@fastify/swagger` library.\n\n### Create Document Options\n\nIf you wish to use [CreateDocumentOptions](https://github.com/samchungy/zod-openapi#createdocumentoptions), pass it in via the plugin options:\n\n```ts\nawait app.register(fastifyZodOpenApiPlugin, {\n  documentOpts: {\n    unionOneOf: true,\n  },\n});\n```\n\n### Custom Response Serializer\n\nThe default response serializer `serializerCompiler` uses [fast-json-stringify](https://github.com/fastify/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.\n\nIf are running into any compatibility issues or wish to restore the previous `JSON.stringify` functionality, you can use the `createSerializerCompiler` function.\n\n```ts\nconst customSerializerCompiler = createSerializerCompiler({\n  stringify: JSON.stringify,\n});\n```\n\n### Error Handling\n\nBy 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.\n\nAs an example:\n\n```json\n{\n  \"code\": \"FST_ERR_VALIDATION\",\n  \"error\": \"Bad Request\",\n  \"message\": \"params/jobId Expected number, received string\",\n  \"statusCode\": 400\n}\n```\n\nFor responses, it will emit a 500 error along with a vague error which will protect your implementation details\n\n```json\n{\n  \"code\": \"FST_ERR_RESPONSE_SERIALIZATION\",\n  \"error\": \"Internal Server Error\",\n  \"message\": \"Response does not match the schema\",\n  \"statusCode\": 500\n}\n```\n\nTo customise this behaviour, you may follow the [fastify error handling](https://fastify.dev/docs/latest/Reference/Validation-and-Serialization/#error-handling) guidance.\n\n#### Request Errors\n\nThis library throws a `RequestValidationError` when a request fails to validate against your Zod Schemas\n\n##### setErrorHandler\n\n```ts\nfastify.setErrorHandler(function (error, request, reply) {\n  if (error.validation) {\n    const zodValidationErrors = error.validation.filter(\n      (err) =\u003e err instanceof RequestValidationError,\n    );\n    const zodIssues = zodValidationErrors.map((err) =\u003e err.params.issue);\n    const originalError = zodValidationErrors?.[0]?.params.error;\n    return reply.status(422).send({\n      zodIssues\n      originalError\n    });\n  }\n});\n```\n\n##### setSchemaErrorFormatter\n\n```ts\nfastify.setSchemaErrorFormatter(function (errors, dataVar) {\n  let message = `${dataVar}:`;\n  for (const error of errors) {\n    if (error instanceof RequestValidationError) {\n      message += ` ${error.instancePath} ${error.keyword}`;\n    }\n  }\n\n  return new Error(message);\n});\n\n// {\n// code: 'FST_ERR_VALIDATION',\n// error: 'Bad Request',\n// message: 'querystring: /jobId invalid_type',\n// statusCode: 400,\n// }\n```\n\n##### attachValidation\n\n```ts\napp.withTypeProvider\u003cFastifyZodOpenApiTypeProvider\u003e().get(\n  '/',\n  {\n    schema: {\n      querystring: z.object({\n        jobId: z.string().openapi({\n          description: 'Job ID',\n          example: '60002023',\n        }),\n      }),\n    },\n    attachValidation: true,\n  },\n  (req, res) =\u003e {\n    if (req.validationError?.validation) {\n      const zodValidationErrors = req.validationError.validation.filter(\n        (err) =\u003e err instanceof RequestValidationError,\n      );\n      console.error(zodValidationErrors);\n    }\n\n    return res.send(req.query);\n  },\n);\n```\n\n#### Response Errors\n\n```ts\napp.setErrorHandler((error, _req, res) =\u003e {\n  if (error instanceof ResponseSerializationError) {\n    return res.status(500).send({\n      error: 'Bad response',\n    });\n  }\n});\n\n// {\n//   error: 'Bad response';\n// }\n```\n\n## Credits\n\n[fastify-type-provider-zod](https://github.com/turkerdev/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.\n\n## Development\n\n### Prerequisites\n\n- Node.js LTS\n- pnpm\n\n```shell\npnpm install\npnpm build\n```\n\n### Test\n\n```shell\npnpm test\n```\n\n### Lint\n\n```shell\n# Fix issues\npnpm format\n\n# Check for issues\npnpm lint\n```\n\n### Release\n\nTo release a new version\n\n1. Create a [new GitHub Release](https://github.com/samchungy/zod-openapi/releases/new)\n2. Select `🏷️ Choose a tag`, enter a version number. eg. `v1.2.0` and click `+ Create new tag: vX.X.X on publish`.\n3. Click the `Generate release notes` button and adjust the description.\n4. Tick the `Set as the latest release` box and click `Publish release`. This will trigger the `Release` workflow.\n5. Check the `Pull Requests` tab for a PR labelled `Release vX.X.X`.\n6. Click `Merge Pull Request` on that Pull Request to update main with the new package version.\n\nTo release a new beta version\n\n1. Create a [new GitHub Release](https://github.com/samchungy/fastify-zod-openapi/releases/new)\n2. 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`.\n3. Click the `Generate release notes` button and adjust the description.\n4. Tick the `Set as a pre-release` box and click `Publish release`. This will trigger the `Prerelease` workflow.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamchungy%2Ffastify-zod-openapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamchungy%2Ffastify-zod-openapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamchungy%2Ffastify-zod-openapi/lists"}