{"id":22096503,"url":"https://github.com/uphold/fastify-openapi-router-plugin","last_synced_at":"2025-07-24T22:32:07.956Z","repository":{"id":251737578,"uuid":"804310108","full_name":"uphold/fastify-openapi-router-plugin","owner":"uphold","description":"A plugin for Fastify to connect routes with OpenAPI 3.x specification.","archived":false,"fork":false,"pushed_at":"2024-11-07T10:00:12.000Z","size":434,"stargazers_count":2,"open_issues_count":2,"forks_count":0,"subscribers_count":23,"default_branch":"master","last_synced_at":"2024-11-07T10:41:24.191Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/uphold.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2024-05-22T11:01:14.000Z","updated_at":"2024-11-07T10:00:17.000Z","dependencies_parsed_at":"2024-08-05T12:48:43.547Z","dependency_job_id":"55385bcb-1243-46c4-b338-d947e733f760","html_url":"https://github.com/uphold/fastify-openapi-router-plugin","commit_stats":null,"previous_names":["uphold/fastify-openapi-router-plugin"],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uphold%2Ffastify-openapi-router-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uphold%2Ffastify-openapi-router-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uphold%2Ffastify-openapi-router-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uphold%2Ffastify-openapi-router-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/uphold","download_url":"https://codeload.github.com/uphold/fastify-openapi-router-plugin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227482485,"owners_count":17779968,"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":[],"created_at":"2024-12-01T04:11:21.032Z","updated_at":"2025-07-24T22:32:07.938Z","avatar_url":"https://github.com/uphold.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @uphold/fastify-openapi-router-plugin\n\n[![Tests](https://github.com/uphold/fastify-openapi-router-plugin/actions/workflows/tests.yaml/badge.svg)](https://github.com/uphold/fastify-openapi-router-plugin/actions/workflows/tests.yaml)\n\nA plugin for [Fastify](https://fastify.dev) to connect routes with a OpenAPI 3.x specification. It does so by:\n\n- Providing a way to register routes using the `operationId` defined in your specification instead of having to manually call `fastify.route` with the correct URL, method, and schema.\n- Handling `securitySchemes` and `security` keywords defined in your specification, simplifying the implementation of authentication and authorization middleware.\n\n## Installation\n\n```bash\nnpm install @uphold/fastify-openapi-router-plugin\n```\n\nThis plugin is written and exported in ESM only. If you are using CommonJS, consider making a pull-request and we will happily review it.\n\n## Usage\n\n```js\nimport Fastify from 'fastify';\nimport openApiRouterPlugin from '@uphold/fastify-openapi-router-plugin';\n\nconst fastify = Fastify();\n\n// Register the OpenAPI Router plugin.\nawait fastify.register(openApiRouterPlugin, {\n  spec: './petstore.json'\n});\n\n// Register a route using the 'operationId'.\nfastify.oas.route({\n  operationId: 'getPetById',\n  handler: async (request, reply) =\u003e {\n    const { petId } = request.params;\n\n    const pet = await retrievePetFromDB(petId);\n\n    return pet;\n  }\n});\n```\n\n### Options\n\nYou can pass the following options during the plugin registration:\n\n```js\nawait fastify.register(import('@fastify/fastify-openapi-router-plugin'), {\n  spec: './petstore.json',\n  securityHandlers: {\n    APIAuth: (value, request) =\u003e {}\n  }\n});\n```\n\n| Option | Type | Description |\n| ------ | ---- | ----------  |\n| `spec` | `string` or `object` | **REQUIRED**. A file path or object of your OpenAPI specification. |\n| `securityHandlers` | `object` | An object containing the security handlers that match [Security Schemes](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object) described in your OpenAPI specification. |\n| `securityErrorMapper` | `function` | A function that allows mapping the default `UnauthorizedError` to a custom error. |\n| `notImplementedErrorMapper` | `function` | A function that allows mapping the default `NotImplementedError` to a custom error. |\n\n#### `spec`\n\nIf you don't provide a valid OpenAPI specification, the plugin will throw an error telling you what's wrong.\n\n**Sample using a file path**\n\n```js\nawait fastify.register(import('@fastify/fastify-openapi-router-plugin'), {\n  spec: './petstore.json' // or spec: './petstore.yaml'\n});\n```\n\n**Sample using an object**\n\n```js\nawait fastify.register(import('@fastify/fastify-openapi-router-plugin'), {\n  spec: {\n    openapi: '3.1.0',\n    ...\n  }\n});\n```\n\n#### `securityHandlers`\n\nIf you haven't defined any [Security Schemes](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object) in your OpenAPI specification, this option won't be required. Otherwise, plugin will try to resolve every `securityHandlers.\u003cname\u003e` as an async function that matches `securitySchemes.\u003cname\u003e` in your OpenAPI specification.\n\nSecurity handlers are executed as a `onRequest` hook for every API operation if plugin founds a [Security Requirement Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-requirement-object) defined on the root level or operation level of your OpenAPI specification. According to [Fastify Lifecycle](https://fastify.dev/docs/latest/Reference/Lifecycle/), it is the most secure way to implement an authentication layer because it avoids parsing the body for unauthorized accesses.\n\nIf your operation's `security` use repeated security schemes, the plugin will call the associated security handler only once per request and cache its result. Furthermore, the plugin is smart enough to skip `security` blocks that have missing values from the request. For example, if you have a `security` block with `APIKey` and `OAuth2` and the request contains the API key but no bearer token, the plugin will automatically skip the block altogether without calling any security handler.\n\nThe security handler should either throw an error or return an object with `{ data, scopes }` where `data` becomes available as `request.oas.security.\u003cname\u003e` in your route handler and `scopes` is array of strings that will be used to verify if the scopes defined in the API operation are satisfied.\n\n**Sample using OAuth 2.0**\n\n```js\nawait fastify.register(import('@fastify/fastify-openapi-router-plugin'), {\n  spec: {\n    openapi: '3.1.0',\n    ...\n    paths: {\n      '/pet/{petId}': {\n        get: {\n          operationId: 'getPetById',\n          ...\n          security: [\n            { OAuth2: ['pets:read'] }\n          ]\n        }\n      }\n    }\n    components: {\n      securitySchemes: {\n        OAuth2: {\n          type: 'oauth2',\n          flows: {\n            ...\n          }\n        }\n      }\n    }\n  },\n  securityHandlers: {\n    OAuth2: async (token, request) =\u003e {\n      // Validate and decode token.\n      const { userId } = verifyToken(token);\n\n      return {\n        data: { userId },\n        scopes: tokenData.scopes\n      };\n    }\n  }\n});\n```\n\nAny error thrown by the security handler will be internally wrapped in a `SecurityHandlerError` with `fatal = true`, which will stop further security blocks to be executed. If you wish to continue with the next security block, you can `throw createSecurityHandlerError(error, false)` in your handler.\n\n\u003e [!TIP]\n\u003e The `scopes` returned by the security handler can contain trailing **wildcards**. For example, if the security handler returns `{ scopes: ['pets:*'] }`, the route will be authorized for any security scope that starts with `pets:`.\n\n\u003e [!IMPORTANT]\n\u003e If your specification uses `http` security schemes with `in: cookie`, you must register [@fastify/cookie](https://github.com/fastify/fastify-cookie) before this plugin.\n\n#### `securityErrorMapper`\n\nThe plugin will throw an `UnauthorizedError` when none of the `security` blocks succeed. By default, this error originates a `401` reply with `{ code: 'FST_OAS_UNAUTHORIZED', message: 'Unauthorized' }` as the payload. You can override this behavior by leveraging the `securityErrorMapper` option:\n\n```js\nawait fastify.register(import('@fastify/fastify-openapi-router-plugin'), {\n  spec: './petstore.json',\n  securityHandlers: {\n    OAuth2: async (request, reply) =\u003e {\n      // ...\n    }\n  },\n  securityErrorMapper: (unauthorizedError) =\u003e {\n    // Use `unauthorizedError.securityReport` to perform logic and return a custom error.\n    return MyUnauthorizedError();\n  },\n});\n```\n\nThe `securityReport` property of the unauthorized error contains an array of objects with the following structure:\n\n```js\n[\n  {\n    ok: false,\n    // Schemes can be an empty object if the security block was skipped due to missing values.\n    schemes: {\n      OAuth2: {\n        ok: false,\n        // The error will be either be a `fastify.oas.errors.SecurityHandlerError` or a\n        // `fastify.oas.errors.ScopesMismatchError` if the scopes were not satisfied.\n        error: \u003cError\u003e,\n      }\n    }\n  }\n]\n```\n\nIf you don't define a `securityErrorMapper`, you can still catch the `UnauthorizedError` in your fastify error handler.\n\n#### `notImplementedErrorMapper`\n\nThe plugin will throw an `NotImplementedError` when you install a handler for not implemented specs through `fastify.oas.installNotImplementedRoutes()` function. By default, this error originates a `501` reply with `{ code: 'FST_OAS_NOT_IMPLEMENTED', message: 'Not implemented' }` as the payload. You can override this behavior by leveraging the `notImplementedErrorMapper` option:\n\n```js\nawait fastify.register(import('@fastify/fastify-openapi-router-plugin'), {\n  spec: './petstore.json',\n  notImplementedErrorMapper: (notImplementedError) =\u003e {\n    return MyNotImplementedError();\n  },\n});\n```\n\nIf you don't define a `notImplementedErrorMapper`, you can still catch the `NotImplementedError` in your fastify error handler.\n\n### Decorators\n\n#### `fastify.oas.route(options)`\n\nThis method is used to register a new route by translating the given `operationId` to a compliant Fastify route.\n\n`options` must be an object containing at least the `operationId` and `handler(request, reply)`. All the available [routes options](https://fastify.dev/docs/latest/Reference/Routes/#routes-options) can be used except `method`, `url` and `schema` because those are loaded from your OpenAPI specification.\n\n**Example**\n\n```js\nawait fastify.register(import('@fastify/fastify-openapi-router-plugin'), {\n  spec: './petstore.json'\n});\n\nfastify.oas.route({\n  operationId: 'getPetById',\n  handler: (request, reply) =\u003e {}\n});\n```\n\n#### `fastify.oas.installNotImplementedRoutes()`\n\nThis function will register handlers with fastify for the routes in the spec that were not registered. You can use this, for example, when you want to give a more specific error for the consumer for operations that you still do not have an implementation.\n\n\u003e [!IMPORTANT]\n\u003e Make sure you `await` the calls to `fastify.register()` for your routes before calling this function to make sure that fastify executes them before the call to `fastify.oas.installNotImplementedRoutes()`.\n\n**Example**\n\n```js\nawait fastify.register(import('@fastify/fastify-openapi-router-plugin'), {\n  spec: './petstore.json'\n});\n\nfastify.oas.route({\n  operationId: 'getPetById',\n  handler: (request, reply) =\u003e {}\n});\n\n// Routes in spec that were not registered through `fastify.oas.route` will get a 'not implemented' handler.\nfastify.oas.installNotImplementedRoutes();\n\n// Finish fastify setup.\nawait fastify.ready();\n```\n\n\u003e [!TIP]\n\u003e If you need to customize the `NotImplementedError` that is thrown by default, you can use the [notImplementedErrorMapper](#notimplementederrormapper) in order to change the default behavior.\n\n#### `fastify.oas.errors`\n\nThis object contains all error classes used by the plugin:\n\n- `SecurityHandlerError`: Used to wrap a security handler error.\n- `ScopesMismatchError`: Used when the scopes returned by the security handler do not satisfy the scopes defined in the API operation.\n- `UnauthorizedError`: Used to indicate that the request is unauthorized, containing a `securityReport`. Check the [`securityErrorMapper`](#securityerrormapper) section for more information.\n\n#### `request.oas`\n\nFor your convenience, the object `request.oas` is populated with data related to the request being made. This is an object containing `{ operation, security, securityReport }`, where:\n\n- `operation` is the raw API operation that activated the Fastify route.\n- `security` is an object where keys are security scheme names and values the returned `data` field from security handlers.\n- `securityReport`: A detailed report of the security verification process. Check the [`securityErrorMapper`](#securityerrormapper) section for more information.\n\n**Example**\n\n```js\nawait fastify.register(import('@fastify/fastify-openapi-router-plugin'), {\n  spec: './petstore.json',\n  securityHandlers: {\n    OAuth2: async (request, reply) =\u003e {\n      // Validate and decode token.\n      const { userId, scopes } = verifyToken(token);\n\n      return {\n        data: { userId },\n        scopes,\n      };\n    }\n  }\n});\n\nfastify.oas.route({\n  operationId: 'getPetById',\n  handler: (request, reply) =\u003e {\n    const { petId } = request.params;\n    const { userId } = request.oas.security.PetStoreAuth;\n\n    return getPetById(petId, userId);\n  }\n});\n```\n\n### Other exports\n\n#### `errors`\n\nThis object contains all error classes that can be thrown by the plugin. It contains the same errors as `fastify.oas.errors`.\n\n#### `verifyScopes(providedScopes, requiredScopes)`\n\nChecks if the `providedScopes` satisfy the `requiredScopes`. Returns an array of missing scopes or an empty array if all scopes are satisfied.\n\nThis functions supports trailing **wildcards** on `providedScopes`. For example, if the provided scopes is `['pets:*']` and the required scopes is `['pets:read']`, the function will return an empty array.\n\n### Caveats\n\n#### Coercing of `parameters`\n\nThis plugin configures Fastify to coerce `parameters` to the correct type based on the schema, [style and explode](https://swagger.io/docs/specification/serialization/) keywords defined in the OpenAPI specification. However, there are limitations. Here's an overview:\n\n- Coercing of all primitive types is supported, like `number` and `boolean`.\n- Coercing of `array` types are supported, albeit with limited styles:\n  - Path: simple.\n  - Query: form with exploded enabled or disabled.\n  - Headers: simple.\n  - Cookies: no support.\n- Coercing of `object` types is not supported.\n\nIf your API needs improved coercion support, like `object` types or `cookie` parameters, please [fill an issue](https://github.com/uphold/fastify-openapi-router-plugin/issues/new) to discuss the implementation.\n\n#### Using `discriminator`\n\nThis plugin removes `discriminator.mapping` from schemas since `ajv` (fastify's validator) does not support it. However, to use `discriminator` in your OpenAPI schema, you must also enable `discriminator` option during fastify initialization like so:\n\n```js\nimport Fastify from 'fastify'\n\nconst fastify = Fastify({\n  ajv: {\n    customOptions: {\n      discriminator: true\n    }\n  }\n});\n```\n\n## License\n\n[MIT](./LICENSE)\n\n## Contributing\n\n### Development\n\nInstall dependencies:\n\n```bash\nnpm i\n```\n\nRun tests:\n\n```bash\nnpm run test\n```\n\nRun tests and update snapshots:\n\n```bash\nnpm run test -- -u\n```\n\n### Cutting a release\n\nThe release process is automated via the [release](https://github.com/uphold/fastify-openapi-router-plugin/actions/workflows/release.yaml) GitHub workflow. Run it by clicking the \"Run workflow\" button.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuphold%2Ffastify-openapi-router-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fuphold%2Ffastify-openapi-router-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuphold%2Ffastify-openapi-router-plugin/lists"}