{"id":19668718,"url":"https://github.com/eropple/fastify-openapi3","last_synced_at":"2026-02-05T03:06:15.249Z","repository":{"id":37639224,"uuid":"459027206","full_name":"eropple/fastify-openapi3","owner":"eropple","description":"Developer-friendly OpenAPI3 tooling for Fastify that's easy to use.","archived":false,"fork":false,"pushed_at":"2026-01-15T18:14:16.000Z","size":563,"stargazers_count":26,"open_issues_count":2,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-01-15T20:49:13.473Z","etag":null,"topics":["fastify","fastify-plugin","json-schema","openapi3","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eropple.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-02-14T05:24:29.000Z","updated_at":"2026-01-11T19:39:38.000Z","dependencies_parsed_at":"2024-01-01T03:37:37.945Z","dependency_job_id":"f33a29ea-54c0-44b5-b3a6-6f48d9083632","html_url":"https://github.com/eropple/fastify-openapi3","commit_stats":null,"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/eropple/fastify-openapi3","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eropple%2Ffastify-openapi3","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eropple%2Ffastify-openapi3/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eropple%2Ffastify-openapi3/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eropple%2Ffastify-openapi3/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eropple","download_url":"https://codeload.github.com/eropple/fastify-openapi3/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eropple%2Ffastify-openapi3/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29108392,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-05T02:48:39.389Z","status":"ssl_error","status_checked_at":"2026-02-05T02:48:27.400Z","response_time":65,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","json-schema","openapi3","typescript"],"created_at":"2024-11-11T16:37:30.178Z","updated_at":"2026-02-05T03:06:15.242Z","avatar_url":"https://github.com/eropple.png","language":"TypeScript","readme":"# `@eropple/fastify-openapi3` #\n_Because I just can't stop making OpenAPI libraries, I guess._\n\n[![NPM version](https://img.shields.io/npm/v/@eropple/fastify-openapi3)](https://www.npmjs.com/package/@eropple/fastify-openapi3) [![CI](https://github.com/eropple/fastify-openapi3/actions/workflows/ci.yaml/badge.svg)](https://github.com/eropple/fastify-openapi3/actions/workflows/ci.yaml)\n\n## What is it? ##\nThis is a library to help you generate [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0)-compliant (or 3.0.3 if you do a little work on your own) specs from your [Fastify](https://www.fastify.io/) app. Others exist, but to my mind don't scratch the itch that the best OAS tooling does: making it faster and easier to create correct specs and valid API clients from those specs. Because of my [own](https://github.com/modern-project/modern-ruby) [background](https://github.com/eropple/nestjs-openapi3) in building OpenAPI libraries, and my growing appreciation for Fastify, I decided to take a crack at it.\n\nThis library presupposes that you use [`@sinclair/typebox`](https://github.com/sinclairzx81/typebox) to define the JSON schema used in your requests, and from that JSON Schema derives types. (Ergonomics for non-TypeScript users is specifically out-of-scope.) It will walk all your routes, determine your schema, and extract and deduplicate those schemas to present a relatively clean and easy-to-use OpenAPI document. It'll then also serve JSON and YAML versions of your specification, as well as host an interactive API explorer with try-it-out features courtesy of [Rapidoc](https://mrin9.github.io/RapiDoc/) or [Scalar](https://scalar.com).\n\n**Fair warning:** This library is in Early Access(tm) and while the functionality that's here does work, there's some functionality that _doesn't_ exist. The stuff that stands out to me personally can be found in [TODO.md](https://github.com/eropple/fastify-openapi3/blob/main/TODO.md), including a short list of things this plugin _won't_ do.\n\n## Usage ##\n\nFirst, install it, etc. etc.:\n\n```bash\nnpm install @eropple/fastify-openapi3\npnpm add @eropple/fastify-openapi3\nyarn add @eropple/fastify-openapi3\n```\n\nOnce you've installed it--well, you'd best go do some things to set it up, huh? There's a manual test (originally added to smoke out issues with Rapidoc serving) in [`examples/start-server.ts`], which can also be directly invoked from the repository with `npm run demo`. Below are the important bits from that demo:\n\n```ts\nimport Fastify, { FastifyInstance } from 'fastify';\nimport { Static, Type } from '@sinclair/typebox';\n\nimport OAS3Plugin, { OAS3PluginOptions, schemaType, oas3PluginAjv } from '../src/index.js';\n```\n\nYour imports. (Obviously, in your project, the last import will be from `\"@eropple/fastify-openapi3\"`.)\n\n```ts\nconst fastifyOpts: FastifyServerOptions = {\n  logger: { level: 'error' },\n  ajv: {\n    plugins: [oas3PluginAjv],\n  }\n}\n\nconst fastify = Fastify(fastifyOpts);\nawait fastify.register(OAS3Plugin, { ...pluginOpts });\n```\n\nRegister the OAS3 plugin. This plugin uses the Fastify logger and can be pretty chatty on `debug`, so bear that in mind. `pluginOpts` is visible in that file for an example, but it's also commented exhaustively for your IntellSensing pleasure while you're writing it.\n\n```ts\nconst PingResponse = schemaType('PingResponse', Type.Object({ pong: Type.Boolean() }));\ntype PingResponse = Static\u003ctypeof PingResponse\u003e;\n```\n\nYour schema. `schemaType` takes a string as a name, which _must be unique for your entire project_, as well as a `@sinclair/typebox` `Type` (which you can then use as a TypeScript type by doing `Static\u003ctypeof T\u003e`, it's awesome). This is now a `TaggedSchema`, which can be used anywhere a normal JSON Schema object can be used within Fastify and will handle validation as you would expect.\n\nIf you use a `TaggedSchema` within another schema, the OAS3 plugin is smart enough to extract it into its own OpenAPI `#/components/schemas/YourTypeHere` entry, so your generated clients will also only have the minimal set of model classes, etc. to worry about. Ditto having them in arrays and so on. I've tried to make this as simple to deal with as possible; if it acts in ways you don't expect, _please file an issue_.\n\nAnd now let's make a route:\n\n```ts\n  await fastify.register(async (fastify: FastifyInstance) =\u003e {\n    fastify.route\u003c{ Reply: PingResponse }\u003e({\n      url: '/ping',\n      method: 'GET',\n      schema: {\n        response: {\n          200: PingResponse,\n        },\n      },\n      oas: {\n        operationId: 'pingPingPingAndDefinitelyNotPong',\n        summary: \"a ping to the server\",\n        description: \"This ping to the server lets you know that it has not been eaten by a grue.\",\n        deprecated: false,\n        tags: ['meta'],\n        vendorPrefixedFields: {\n          \"x-my-vendor-field\": true,\n        },\n      },\n      handler: async (req, reply) =\u003e {\n        return { pong: true };\n      }\n    });\n  }, { prefix: '/api' });\n```\n\nYou don't have to put yours inside a prefixed route, but I like to, so, well, there you go.\n\nIf you do a `npm run demo`, you'll get a UI that looks like the following:\n\n![a docs screenshot](https://i.imgur.com/iOPApmq.png)\n\nAnd there you go.\n\n## Autowired Security ##\n\nThis plugin includes an autowired security system that automatically handles authentication based on your OpenAPI security schemes. Define your security schemes once, and the plugin will automatically validate credentials on routes that use them.\n\n### Basic Setup ###\n\n```ts\nawait fastify.register(OAS3Plugin, {\n  openapiInfo: { title: 'My API', version: '1.0.0' },\n  autowiredSecurity: {\n    securitySchemes: {\n      ApiKey: {\n        type: 'apiKey',\n        in: 'header',\n        name: 'X-Api-Key',\n        fn: (apiKey, request) =\u003e {\n          return apiKey === 'secret' ? { ok: true } : { ok: false, code: 401 };\n        },\n      },\n      BearerAuth: {\n        type: 'http',\n        scheme: 'bearer',\n        fn: (token, request) =\u003e {\n          // Validate JWT or other bearer token\n          return isValidToken(token) ? { ok: true } : { ok: false, code: 401 };\n        },\n      },\n    },\n  },\n});\n\n// Use security on routes\nfastify.get('/protected', {\n  oas: {\n    security: { ApiKey: [] },\n  },\n}, async (req, reply) =\u003e {\n  return { data: 'secret stuff' };\n});\n```\n\n### Handler Return Values ###\n\nSecurity handlers return `{ ok: true }` for success, or `{ ok: false, code: 401 | 403 }` for failure:\n- Return `401` (Unauthorized) when credentials are missing or invalid\n- Return `403` (Forbidden) when credentials are valid but access is denied\n\n### AND/OR Security Logic ###\n\nOpenAPI supports combining security requirements:\n\n```ts\n// OR logic: either scheme can grant access\noas: {\n  security: [{ ApiKey: [] }, { BearerAuth: [] }],\n}\n\n// AND logic: both schemes must pass\noas: {\n  security: { ApiKey: [], BearerAuth: [] },\n}\n```\n\n### Optional Credentials with `passNullIfNoneProvided` ###\n\nBy default, missing credentials return 401 immediately. Set `passNullIfNoneProvided: true` to receive `null` in your handler instead, allowing you to implement optional authentication:\n\n```ts\nsecuritySchemes: {\n  OptionalApiKey: {\n    type: 'apiKey',\n    in: 'header',\n    name: 'X-Api-Key',\n    passNullIfNoneProvided: true,\n    fn: (apiKey, request) =\u003e {\n      if (apiKey === null) {\n        // No key provided - allow anonymous access\n        return { ok: true };\n      }\n      // Key provided - validate it\n      return apiKey === 'secret' ? { ok: true } : { ok: false, code: 401 };\n    },\n  },\n}\n```\n\n### Accessing Request Body with `requiresParsedBody` ###\n\nSometimes authentication decisions depend on the request body (e.g., request signing, body-based authorization). Set `requiresParsedBody: true` to receive the parsed body in your handler:\n\n```ts\nsecuritySchemes: {\n  BodyAwareAuth: {\n    type: 'apiKey',\n    in: 'header',\n    name: 'X-Api-Key',\n    requiresParsedBody: true,\n    fn: (apiKey, request, context) =\u003e {\n      // context.body contains the parsed request body\n      const body = context?.body as { resourceId?: string };\n\n      // Check if user has access to the requested resource\n      if (!userCanAccessResource(apiKey, body?.resourceId)) {\n        return { ok: false, code: 403 };\n      }\n      return { ok: true };\n    },\n  },\n}\n```\n\nWhen `requiresParsedBody` is not set or is `false`, the `context` parameter will be `undefined`.\n\n### Raw Body Access for Signature Validation ###\n\nFor webhook signature validation or HMAC verification, you need access to the raw (unparsed) request body. Use the [`fastify-raw-body`](https://github.com/Eomm/fastify-raw-body) plugin alongside `requiresParsedBody`:\n\n```ts\nimport fastifyRawBody from 'fastify-raw-body';\n\n// Register fastify-raw-body BEFORE the OAS plugin\nawait fastify.register(fastifyRawBody, {\n  global: false,  // Only on routes that need it\n  runFirst: true, // Get raw body before any transforms\n});\n\nawait fastify.register(OAS3Plugin, {\n  // ... other options\n  autowiredSecurity: {\n    securitySchemes: {\n      WebhookSignature: {\n        type: 'apiKey',\n        in: 'header',\n        name: 'X-Signature',\n        requiresParsedBody: true,\n        fn: (signature, request, context) =\u003e {\n          // request.rawBody contains the raw string/buffer\n          const expectedSig = crypto\n            .createHmac('sha256', secret)\n            .update(request.rawBody as string)\n            .digest('hex');\n\n          if (signature !== expectedSig) {\n            return { ok: false, code: 401 };\n          }\n          return { ok: true };\n        },\n      },\n    },\n  },\n});\n\n// Enable rawBody on specific routes\nfastify.post('/webhook', {\n  config: { rawBody: true },\n  oas: { security: { WebhookSignature: [] } },\n}, handler);\n```\n\n### Supported Security Schemes ###\n\n- **API Key** (`type: 'apiKey'`): Validates keys from headers or cookies\n- **HTTP Basic** (`type: 'http', scheme: 'basic'`): Validates username/password credentials\n- **HTTP Bearer** (`type: 'http', scheme: 'bearer'`): Validates bearer tokens\n\n## Contributing ##\nIssues and PRs welcome! Constructive criticism on how to improve the library would be awesome, even as I use it in my own stuff and figure out where to go from there, too.\n\n**Before you start in on a PR, however**, please do me a solid and drop an issue so we can discuss the approach. Thanks!\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feropple%2Ffastify-openapi3","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feropple%2Ffastify-openapi3","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feropple%2Ffastify-openapi3/lists"}