├── .github └── FUNDING.yml ├── .gitignore ├── .openapiconfig ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.js ├── blog ├── 2019-05-28-first-blog-post.md └── authors.yml ├── docs ├── api-first.md ├── examples │ ├── _category_.json │ ├── boilerplate.md │ ├── building-apis.md │ ├── calling-apis.md │ ├── tanstack-query.md │ └── testing-react-with-jest-and-openapi-mocks.md ├── intro.md ├── openapi-backend │ ├── _category_.json │ ├── api.md │ ├── examples.md │ ├── intro.md │ ├── mocking.md │ ├── operation-handlers.md │ ├── request-validation.md │ ├── response-validation.md │ ├── security-handlers.md │ ├── typescript.md │ └── versioning.md ├── openapi-client-axios │ ├── _category_.json │ ├── api.md │ ├── bundling.md │ ├── intro.md │ ├── typegen.md │ └── usage.md └── openapicmd │ ├── _category_.json │ ├── call.md │ ├── config.md │ ├── generating-documentation.md │ ├── intro.md │ ├── mock-server.md │ └── typegen.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src ├── components │ ├── Comparisons.mdx │ ├── HomepageFeatures │ │ ├── GithubStarsButton.tsx │ │ └── index.tsx │ └── Sandbox │ │ ├── Iframe.tsx │ │ └── Sandbox.tsx ├── css │ └── custom.css └── pages │ ├── docs │ ├── examples.tsx │ ├── index.tsx │ ├── openapi-backend.tsx │ ├── openapi-client-axios.tsx │ └── openapicmd.tsx │ ├── imprint.mdx │ ├── index.module.css │ └── index.tsx ├── static ├── CNAME ├── img │ ├── favicon.ico │ ├── header.png │ ├── intellisense.gif │ ├── openapi-stack-logo.png │ ├── openapi-stack.drawio.png │ ├── openapistack-social.jpg │ ├── redoc-screenshot.png │ ├── sponsors │ │ └── fern_logo_tagline.png │ ├── swagger-ui-screenshot.png │ ├── undraw_code_inspection_bdl7.svg │ ├── undraw_developer_activity_re_39tg.svg │ └── undraw_secure_login_pdn4.svg └── petstore.openapi.json ├── tailwind.config.js └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: anttiviljami 2 | open_collective: openapi-stack 3 | custom: 4 | - https://buymeacoff.ee/anttiviljami 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /.openapiconfig: -------------------------------------------------------------------------------- 1 | definition: https://openapistack.co/petstore.openapi.json 2 | security: 3 | api_key: 4 | header: 5 | api_key: secret123 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | support@openapistack.co. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | OpenAPI Stack is Free and Open Source Software. Issues and pull requests are more than welcome! 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Viljami Kuosmanen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | openapi-stack 3 |

openapistack.co

4 |

Full stack typesafe API-first development for REST.

5 | 6 | [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/openapistack/docs/blob/master/LICENSE) 7 | [![npm downloads](https://img.shields.io/npm/dw/openapi-backend)](https://www.npmjs.com/package/openapi-backend) 8 | [![npm downloads](https://img.shields.io/npm/dw/openapi-client-axios)](https://www.npmjs.com/package/openapi-client-axios) 9 | [![Buy me a coffee](https://img.shields.io/badge/donate-buy%20me%20a%20coffee-orange)](https://buymeacoff.ee/anttiviljami) 10 | 11 |
12 | 13 | **openapi-stack** is a collection of open source libraries and tools for full stack software development using [OpenAPI specification](https://www.openapis.org/) with an API Design First philosophy. 14 | 15 | The goal is to unlock great developer experience and full stack type safety for software teams using REST; inspired by tools like [GraphQL](https://graphql.org/) and [tRPC](https://trpc.io). 16 | 17 | ## Benefits 18 | 19 | 1. 🚀 **No code generation.** Write your own code the way you like it. Only generate types from OpenAPI spec if you want. 20 | 1. 🤝 **Single source of truth for your API contract.** No more manually updating your OpenAPI specs to keep up with your backend code. Ensure your API docs and SDKs stay up to date by using the spec in runtime to route and validate. 21 | 1. 🧙‍♂️ **Type safety and validation.** Build your product faster and with a better developer experience using strongly typed Typescript and code autocomplete both in the server and client side. 22 | 1. ❤️ **Testing & Collaboration.** Leverage API mocks to make testing and development easier and iterate fast on your API design as you build your app's interface. Being blocked by the backend team is a thing of the past! 23 | 24 | ## Packages part of openapi-stack: 25 | 26 | - [openapistack/openapi-backend ![GitHub Repo stars](https://img.shields.io/github/stars/openapistack/openapi-backend?style=social)](https://github.com/openapistack/openapi-backend) 27 | - [openapistack/openapi-client-axios ![GitHub Repo stars](https://img.shields.io/github/stars/openapistack/openapi-client-axios?style=social)](https://github.com/openapistack/openapi-client-axios) 28 | - [openapistack/openapicmd ![GitHub Repo stars](https://img.shields.io/github/stars/openapistack/openapicmd?style=social)](https://github.com/openapistack/openapicmd) 29 | 30 | ## Comparisons 31 | 32 |
33 | How does openapi-stack compare to GraphQL? 34 | 35 | [*GraphQL*](https://graphql.org/) is a query language for APIs developed by Facebook. It gives API clients full control over the data they query, making it extremely flexible and efficient for client-centric use cases. 36 | 37 | Similar to [OpenAPI specification](https://www.openapis.org/), GraphQL APIs define a strongly typed schema for the data and mutations they support which makes them discoverable and intuitive to develop against. 38 | 39 | OpenAPI stack achieves the same type safety and great developer experience by using the OpenAPI specification as a single source of truth for the API contract, used to generate types for both client and server side and utilising it for routing and validation during runtime. 40 | 41 | Both GraphQL and openapi-stack encourage an [API First](https://openapistack.co/docs/api-first/) approach where the API contract is treated as a first class citizen in software design instead of treating it as merely documentation. 42 | 43 | While REST APIs don't generally provide the same level of control to clients as GraphQL, many times this could be seen as a benefit especially in scenarios where strict control over data access and operations is crucial. 44 | 45 | Many organizations choose REST over GraphQL due to more established conventions, simplicity, and the ability to leverage standard HTTP features directly. Widespread knowledge around REST contribute to its choice among organizations looking for a tried-and-tested approach to building APIs. 46 |
47 | 48 |
49 | How does openapi-stack compare to tRPC? 50 | 51 | [tRPC](https://trpc.io/) is a *Remote Procedure Call* (RPC) library for Typescript to build and consume typesafe APIs. 52 | 53 | Designed for full-stack Typescript applications, tRPC allows direct sharing of types between both the client and server, without relying on code generation. 54 | 55 | Unlike GraphQL and REST, tRPC doesn't expose a standard machine-readable API schema to be consumed by clients, instead taking a more straightforward approach of exposing endpoints or *procedures*, essentially [*"just functions"*](https://trpc.io/docs/concepts#its-just-functions) invoked by the client to the server. 56 | 57 | OpenAPI stack achieves type safety using a similar workflow to tRPC's procedures with [*OpenAPI operations*](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operation-object), also avoiding code generation by only generating types from OpenAPI spec and using the machine readable contract in the runtime for routing and validation. 58 | 59 | While the lightweight tRPC approach is optimal for teams just looking to build full stack applications, teams looking to build robust APIs are better served by the API design first approach of openapi-stack or GraphQL. 60 | 61 |
62 | 63 | ## Features 64 | 65 | - [x] Battle-tested in production. High test coverage. 66 | - [x] ️No code generation – we only generate types 67 | - [x] Built with TypeScript, types included with full autocomplete support 68 | - [x] Framework agnostic – works with your stack 69 | - [x] Lightweight - small frontend bundle + optimized for serverless cold starts 70 | - [x] OpenAPI 3.x support 71 | - [x] [Samples](https://openapistack.co/docs/examples/boilerplate) included 72 | 73 | ## Star History 74 | 75 | [![Star History Chart](https://api.star-history.com/svg?repos=openapistack/openapi-backend,openapistack/openapi-client-axios,openapistack/openapicmd,openapistack/docs&type=Date)](https://star-history.com/#openapistack/openapi-backend&openapistack/openapi-client-axios&openapistack/openapicmd&openapistack/docs&Date) 76 | 77 | ## Commercial support 78 | 79 | For assistance with integrating openapi-stack for your company, reach out at support@openapistack.co. 80 | 81 | ## Contributing 82 | 83 | OpenAPI Stack is Free and Open Source Software. Issues and pull requests are more than welcome! 84 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /blog/2019-05-28-first-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: first-blog-post 3 | title: First Blog Post 4 | authors: [anttiviljami] 5 | tags: [openapi] 6 | --- 7 | 8 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 9 | -------------------------------------------------------------------------------- /blog/authors.yml: -------------------------------------------------------------------------------- 1 | anttiviljami: 2 | name: Viljami Kuosmanen 3 | title: OpenAPI Stack Creator 4 | url: https://github.com/anttiviljami 5 | image_url: https://github.com/anttiviljami.png 6 | -------------------------------------------------------------------------------- /docs/api-first.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Why API First? 3 | hide_title: true 4 | sidebar_position: 1 5 | --- 6 | 7 | # Why API First? 8 | 9 | ## Schema First 10 | 11 | The core idea of _API First_, sometimes referred to as _Schema First_, is that software teams start by defining an API contract and use it as the single source of truth for data models in their application logic. 12 | 13 | Teams using this approach define their API contracts using machine-readable specifications like [OpenAPI](https://www.openapis.org/) or [GraphQL](https://graphql.org/), and leverage techniques like [Generating Types](/docs/openapicmd/typegen) and [API Mocking](/docs/openapicmd/mock-server/) to rapidly iterate the product and API design while making sure the implementation and documentation stay up to date with the API contract. 14 | 15 | We do this to collaborate effectively on software design, making changes to the API schema when needed, using shared types and automated tests to ensure our implementation follows the API contract. **This reduces bugs and allows teams to deliver continuously.** 16 | 17 | ## Type Safety 18 | 19 | Use of typed languages like TypeScript improve developer experience and reduce bugs by providing strict type checks and code autocomplete during development. This is especially powerful when types are shared and used across the stack in both backend implementation and client-side logic. 20 | 21 | Given that the OpenAPI specification already leverages [JSON Schema](https://json-schema.org/) for defining data model types, these can be effortlessly translated into TypeScript types for coding use. 22 | 23 | :::tip 24 | 25 | OpenAPI Stack provides the [`openapi typegen`](/docs/openapicmd/typegen/) CLI command to generate types from OpenAPI schema, to keep your implementation up to date with the API contract. 26 | 27 | ::: 28 | 29 | ## Design First 30 | 31 | Introducing [API Mocking](/docs/openapicmd/mock-server/) enables developers working on the application's frontend to develop the app against a mocked version of the backend which can be cheaply adjusted by fine-tuning the API schema. **This means the frontend team is never blocked waiting for backend changes.** 32 | 33 | For customer-centric agile teams, focusing on the user facing parts of the application first is a great way to rapidly prototype designs before investing into implementing backend logic. 34 | 35 | **Design First** signifies that design drives the code, not the other way around. 36 | 37 |
38 | API First Cycle 39 |
40 | -------------------------------------------------------------------------------- /docs/examples/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Examples", 3 | "position": 6 4 | } -------------------------------------------------------------------------------- /docs/examples/boilerplate.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Boilerplate projects 3 | sidebar_position: 10 4 | --- 5 | 6 | :::tip 7 | 8 | See [Framework Examples](/docs/openapi-backend/examples/) for how openapi-backend integrates with any Node.js server or framework. 9 | 10 | ::: 11 | 12 | A list of example projects using openapi-stack with different frameworks: 13 | 14 | - **Next.js** 15 | - openapi-stack-fullstack-nextjs-starter ([GitHub](https://github.com/anttiviljami/docs-nextjs-starter), [Playground](https://stackblitz.com/fork/openapi-stack-nextjs-starter?file=public%2Fopenapi.yml&file=app%2Fpage.tsx&file=pages%2Fapi%2F%5Bopenapi%5D.ts)) 16 | - **Express** 17 | - openapi-backend-express ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/express)) 18 | - openapi-backend-express-typescript ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/express-typescript)) 19 | - openapi-backend-express-ts-mock ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/express-ts-mock)) 20 | - openapi-backend-express-apikey-auth ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/express-apikey-auth)) 21 | - openapi-backend-express-jwt-auth ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/express-jwt-auth)) 22 | - **SST** 23 | - openapi-stack-sst-sample ([GitHub](https://github.com/anttiviljami/openapistack-sst-sample)) 24 | - openapi-backend-sst-sample ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/aws-sst)) 25 | - **Hapi** 26 | - openapi-backend-hapi-typescript ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/hapi-typescript)) 27 | - **Koa** 28 | - openapi-backend-koa ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/koa)) 29 | - **Fastify** 30 | - openapi-backend-fastify ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/fastify)) 31 | - **AWS SAM** 32 | - openapi-backend-aws-sam ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/aws-sam)) 33 | - **AWS CDK** 34 | - openapi-backend-aws-cdk ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/aws-cdk)) 35 | - **Serverless Framework** 36 | - openapi-backend-serverless-aws ([Github](https://github.com/openapistack/openapi-backend/tree/main/examples/serverless-framework)) 37 | - **Azure Function** 38 | - openapi-backend-azure-function ([GitHub](https://github.com/openapistack/openapi-backend/tree/main/examples/azure-function)) 39 | -------------------------------------------------------------------------------- /docs/examples/building-apis.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Building APIs 3 | sidebar_position: 1 4 | --- 5 | 6 | :::info 7 | 8 | In this example, we will design and build a minimal Node.js REST API using [openapi-backend](/docs/openapi-backend) and the [express](https://expressjs.com) framework. 9 | 10 | ::: 11 | 12 | :::tip 13 | 14 | Not using express? The `openapi-backend` package can be used with any other Node.js framework or server. See [boilerplate projects](/docs/examples/boilerplate/) for examples of using OpenAPI stack with other frameworks. 15 | 16 | ::: 17 | 18 | ## Prerequisites 19 | 20 | This guide assumes you already know how to set up a Node.js project with Typescript. You can find a minimal sample project [here](https://github.com/openapistack/openapi-backend/tree/main/examples/express-typescript). 21 | 22 | Before starting, make sure to install `openapi-backend` and `express` as dependencies: 23 | 24 | ``` 25 | npm i openapi-backend express 26 | ``` 27 | 28 | ## Setting up express 29 | 30 | We will start by setting up a basic express server listening on port `9000`: 31 | 32 | ```ts 33 | // src/server.ts 34 | import express from 'express'; 35 | 36 | const app = express(); 37 | 38 | // use the json middleware 39 | app.use(express.json()); 40 | 41 | // start server 42 | app.listen(9000, () => console.info('api listening at http://localhost:9000')); 43 | ``` 44 | 45 | ## Setting up openapi-backend 46 | 47 | We can then import `openapi-backend` and initialize it with an openapi definition file: 48 | 49 | ```ts 50 | import { OpenAPIBackend } from 'openapi-backend'; 51 | 52 | const api = new OpenAPIBackend({ 53 | definition: './openapi.yml', 54 | }); 55 | 56 | api.init(); 57 | ``` 58 | 59 | ## Writing our API spec 60 | 61 | We load our API definition from `openapi.yml`, so let's populate it with a simple API design with a `getPets` operation: 62 | 63 | ```yaml 64 | # src/openapi.yml 65 | openapi: 3.0.2 66 | info: 67 | title: "Pet API" 68 | version: 1.0.0 69 | paths: 70 | "/pets": 71 | get: 72 | operationId: getPets 73 | responses: 74 | "200": 75 | description: list of pets 76 | content: 77 | application/json: 78 | schema: 79 | type: array 80 | items: 81 | $ref: "#/components/schemas/Pet" 82 | components: 83 | schemas: 84 | Pet: 85 | type: object 86 | properties: 87 | id: 88 | type: string 89 | type: 90 | type: string 91 | enum: ["cat", "dog"] 92 | name: 93 | type: string 94 | required: ["id", "type"] 95 | ``` 96 | 97 | ## Implementing Handlers 98 | 99 | Let's then implement an [operation handler](/docs/openapi-backend/operation-handlers/) for the `getPets` operation defined in our spec. 100 | 101 | ```ts 102 | api.register('getPets', async (c, req: express.Request, res: express.Response) => 103 | res.status(200).json([{ id: '1', type: 'cat', name: 'Garfield' }]) 104 | ) 105 | ``` 106 | 107 | To enable routing and validation, we'll add some default handlers in our code for common exceptions: 108 | 109 | ```ts 110 | // return 400 when request validation fails 111 | api.register('validationFail', (c, req: express.Request, res: express.Response) => 112 | res.status(400).json({ err: c.validation.errors }), 113 | ) 114 | // return 404 when route doesn't match any operation in openapi.yml 115 | api.register('notFound', (c, req: express.Request, res: express.Response) => 116 | res.status(404).json({ err: 'not found' }), 117 | ) 118 | ``` 119 | 120 | 121 | ## Use as express middleware 122 | 123 | Finally we wire up openapi-backend to route, validate and handle API requests with express: 124 | 125 | ```ts 126 | app.use((req, res) => api.handleRequest(req, req, res)); 127 | ``` 128 | 129 | ## Full Example 130 | 131 | Putting everything together, here is our complete example server code: 132 | 133 | ```ts 134 | // src/server.ts 135 | import { OpenAPIBackend, Request } from 'openapi-backend'; 136 | import express from 'express'; 137 | 138 | const api = new OpenAPIBackend({ 139 | definition: './openapi.yml', 140 | }); 141 | 142 | api.init(); 143 | 144 | // handler for getPets operation in openapi.yml 145 | api.register('getPets', async (c, req: express.Request, res: express.Response) => 146 | res.status(200).json([{ id: '1', type: 'cat', name: 'Garfield' }]) 147 | ) 148 | // return 400 when request validation fails 149 | api.register('validationFail', (c, req: express.Request, res: express.Response) => 150 | res.status(400).json({ err: c.validation.errors }), 151 | ) 152 | // return 404 when route doesn't match any operation in openapi.yml 153 | api.register('notFound', (c, req: express.Request, res: express.Response) => 154 | res.status(404).json({ err: 'not found' }), 155 | ) 156 | 157 | const app = express(); 158 | 159 | // use the json middleware 160 | app.use(express.json()); 161 | 162 | // use openapi-backend to handle requests 163 | app.use((req, res) => api.handleRequest(req as Request, req, res)); 164 | 165 | // start server 166 | app.listen(9000, () => console.info('api listening at http://localhost:9000')); 167 | ``` 168 | 169 | ## Optional: Mocking Responses 170 | 171 | Instead of implementing a handler for `getPets`, you can register a `notImplemented` handler to mock the response based 172 | on the OpenAPI schema: 173 | 174 | ```ts 175 | // mock responses for operations with no registered handlers 176 | api.register('notImplemented', (c, req: express.Request, res: express.Response) => { 177 | const { status, mock } = c.api.mockResponseForOperation(c.operation.operationId); 178 | return res.status(status).json(mock); 179 | }); 180 | ``` -------------------------------------------------------------------------------- /docs/examples/calling-apis.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Invoking APIs 3 | sidebar_position: 2 4 | --- 5 | 6 | :::info 7 | 8 | In this example we will write code to interact with a public mock API available on [example.openapistack.co/openapi.json](https://example.openapistack.co/openapi.json) 9 | 10 | ::: 11 | 12 | :::tip 13 | 14 | If you're looking to invoke APIs via CLI, see [`openapicmd call`](/docs/openapicmd/call/) 15 | 16 | ::: 17 | 18 | ## Prerequisites 19 | 20 | Before starting, make sure to install `openapi-client-axios` and `axios` as dependencies in your project: 21 | 22 | ``` 23 | npm i openapi-client-axios axios 24 | ``` 25 | 26 | ## Creating a client instace 27 | 28 | To call our API, we import `openapi-client-axios` and configure it by passing the OpenAPI definition URL: 29 | 30 | ```ts 31 | import { OpenAPIClientAxios } from 'openapi-client-axios'; 32 | 33 | const api = new OpenAPIClientAxios({ 34 | definition: 'https://example.openapistack.co/openapi.json', 35 | }); 36 | ``` 37 | 38 | :::note 39 | For optimal performance, it's recommended to pass the definition as a JS object instead or fetching it from a URL in runtime. 40 | ::: 41 | 42 | To initialise our client instance, we call `api.init()`: 43 | 44 | ```ts 45 | const client = await api.init(); 46 | ``` 47 | 48 | ## Adding Types 49 | 50 | For type-safety and code autocompletion we use the CLI command `openapicmd typegen` to generate types. 51 | 52 | This command will create a file named `openapi.d.ts` in the src directory: 53 | 54 | ```sh 55 | npx openapicmd typegen https://example.openapistack.co/openapi.json > src/openapi.d.ts 56 | ``` 57 | 58 | We can now import the types and use them to create our fully typed API client by passing the `Client` type to our `init` call. 59 | 60 | ```ts 61 | import type { Client } from './openapi.d.ts'; 62 | 63 | const client = await api.init(); 64 | ``` 65 | 66 | ## Invoking the API 67 | 68 | Finally, we are ready to call our API using [operation methods](/docs/openapi-client-axios/usage/#operation-methods) based on our `openapi.yml` spec: 69 | 70 | ```ts 71 | const petsResponse = await client.getPets(); 72 | 73 | const pets = petsResponse.data; // Pet[] inferred as type as defined in the API 74 | ``` 75 | 76 | ## Full Example 77 | 78 | Putting everything together, here is our full code example combining all the steps: 79 | 80 | ```ts 81 | // src/example.ts 82 | import { OpenAPIClientAxios } from 'openapi-client-axios'; 83 | import type { Client } from './openapi.d.ts'; 84 | 85 | const api = new OpenAPIClientAxios({ 86 | definition: 'https://example.openapistack.co/openapi.json' 87 | }); 88 | 89 | async function main() { 90 | const client = await api.init(); 91 | 92 | const petsResponse = await client.getPets(); 93 | const pets = petsResponse.data; // Pet[] inferred as type 94 | console.log('getPets response', petsResponse.status, pets); 95 | } 96 | main(); 97 | ``` 98 | -------------------------------------------------------------------------------- /docs/examples/tanstack-query.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: Use with React Query 4 | --- 5 | 6 | To use `openapi-client-axios` in a declarative way in the frontend, we recommend using [TanStack Query](https://tanstack.com/query/latest) (previously known as React Query) together with type safe clients created with openapi-stack. 7 | 8 | ## Example with React Query 9 | 10 | First, let's set up our type safe API client: 11 | 12 | :::tip 13 | Use the [`openapicmd typegen`](/docs/openapicmd/typegen/) command to generate the `openapi.d.ts` type file for your API. 14 | ::: 15 | 16 | :::note 17 | For optimal performance, it's recommended to pass the definition as a JS object instead or fetching it from a URL in runtime. 18 | ::: 19 | 20 | ```ts 21 | // api.ts 22 | import { OpenAPIClientAxios } from 'openapi-client-axios'; 23 | import type { Client } from './openapi.d.ts'; 24 | 25 | const api = new OpenAPIClientAxios({ 26 | definition: 'https://example.openapistack.co/openapi.json', 27 | }); 28 | 29 | export const getApiClient = async () => { 30 | const client = await api.getClient(); 31 | 32 | // add auth token 33 | client.default.headers['authorization'] = `Bearer ${API_TOKEN}`; 34 | 35 | return client; 36 | } 37 | ``` 38 | 39 | Now we are ready to use our type safe client with React Query: 40 | 41 | ```tsx 42 | // PetView.tsx 43 | import { useQuery } from 'react-query'; 44 | import { getApiClient } from './api'; 45 | import Loader from './Loader'; 46 | 47 | export const PetView = (props: { petId: string }) => { 48 | const petQuery = useQuery( 49 | ['getPetById', props.petId], 50 | () => getApiClient() 51 | .then(client => client.getPetById(props.petId)) 52 | .then(res => res.data), 53 | { enabled: !!props.petId }, 54 | ); 55 | 56 | const pet = petQuery.data; // type Pet is inferred from openapi.d.ts 57 | 58 | return ( 59 |
60 | {petQuery.fetching && } 61 | 62 | {petQuery.data && (/* TODO: show pet information */)} 63 |
64 | ) 65 | } 66 | ``` -------------------------------------------------------------------------------- /docs/examples/testing-react-with-jest-and-openapi-mocks.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Testing with msw + openapi-backend mocks 6 | 7 | :::info 8 | 9 | [MSW](https://mswjs.io/) (Mock Service Worker) is a popular library built to intercept and mock network requests commonly used in testing. 10 | 11 | ::: 12 | 13 | ### Setting up msw 14 | 15 | Here is the basic setup for a rest api mock using msw: 16 | 17 | ```javascript 18 | import { rest } from "msw"; 19 | import { setupServer } from "msw/node"; 20 | 21 | const server = setupServer( 22 | rest.get("/api/pets", (req, res, ctx) => { 23 | const pets = [{ id: 1, name: "Garfield", type: "cat" }]; 24 | return res(ctx.json({ pets })); 25 | }) 26 | ); 27 | 28 | beforeAll(() => server.listen()); 29 | afterAll(() => server.close()); 30 | ``` 31 | 32 | One of the reasons this is extremely cool is because it avoids the pain of having to start up a real local mock backend, such as an express server that needs to be bound to a specific port on the host running the test. 33 | 34 | This helps keep your tests fast and simple to run, as they should be. 35 | 36 | ### Even better with OpenAPI 37 | 38 | It turns out `msw` together with `openapi-backend` is the perfect combination for mocking REST apis. 39 | 40 | To provide a full mock for an API, all we need is to create a mock backend with openapi-backend using the API definition and tell msw to use it: 41 | 42 | ```javascript 43 | import { rest } from "msw"; 44 | import { setupServer } from "msw/node"; 45 | import OpenAPIBackend from "openapi-backend"; 46 | import definition from "./path/to/definition.json"; 47 | 48 | // create our mock backend with openapi-backend 49 | const api = new OpenAPIBackend({ definition }); 50 | api.register("notFound", (c, res, ctx) => res(ctx.status(404))); 51 | api.register("notImplemented", async (c, res, ctx) => { 52 | const { status, mock } = api.mockResponseForOperation( 53 | c.operation.operationId 54 | ); 55 | ctx.status(status); 56 | return res(ctx.json(mock)); 57 | }); 58 | 59 | // tell msw to intercept all requests to api/* with our mock 60 | const server = setupServer( 61 | rest.all("/api/*", async (req, res, ctx) => 62 | api.handleRequest( 63 | { 64 | path: req.url.pathname, 65 | query: req.url.search, 66 | method: req.method, 67 | body: req._bodyUsed ? await req.json() : null, 68 | headers: { ...req.headers.raw }, 69 | }, 70 | res, 71 | ctx 72 | ) 73 | ) 74 | ); 75 | 76 | beforeAll(() => server.listen()); 77 | afterAll(() => server.close()); 78 | ``` 79 | 80 | Now instead of having to write your own mock handlers for each operation, they're generated from the response schemas and examples defined in the OpenAPI document. 81 | 82 | What's more: any time the API definition changes, all your mocks will be automatically updated giving you further confidence your app is compatible with the new API version. 83 | 84 | ### Enabling Request Validation 85 | 86 | When testing, it's often very useful to make sure your application is actually sending the correct requests to the API. 87 | 88 | Working with OpenAPI definitions has the benefit that API operations are well defined and requests can be automatically validated using JSON schema. 89 | 90 | To enable request validation during tests, you can simply register the [validationFail handler](https://openapistack.co/docs/openapi-backend/api#validationfail-handler) for openapi-backend: 91 | 92 | ```javascript 93 | api.register("validationFail", (c, res, ctx) => 94 | res(ctx.status(400), ctx.json({ error: c.validation.errors })) 95 | ); 96 | ``` 97 | 98 | When running tests, a malformed call to an API endpoint will now result in a 400 Bad Request error from the mock backend, alongside a useful error message telling you what's wrong with the request. 99 | 100 | ### Custom Handlers 101 | 102 | In some tests it might make sense to provide a different mock than the default one as provided by openapi-backend. 103 | 104 | Registering your own mock for an API operation in a test is as simple as calling `api.register()` with the operationId and a mock handler: 105 | 106 | ```javascript 107 | it("should call getPets operation", () => { 108 | // given 109 | const mockResponse = [{ id: 2, name: "Odie" }]; 110 | const mockHandler = jest.fn((c, res, ctx) => res(ctx.json(mockResponse))); 111 | api.register("getPets", mockHandler); 112 | 113 | // when 114 | // render()... 115 | 116 | // then 117 | expect(mockHandler).toBeCalled(); 118 | }); 119 | ``` 120 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | hide_title: true 4 | sidebar_position: 0 5 | --- 6 | 7 |
8 | 9 | openapicmd logo 10 | 11 |

12 | openapi-stack 13 | GitHub 14 |

15 | 16 | [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/openapistack/docs/blob/master/LICENSE) 17 | [![npm downloads](https://img.shields.io/npm/dw/openapi-backend?label=backend)](https://www.npmjs.com/package/openapi-backend) 18 | [![npm downloads](https://img.shields.io/npm/dw/openapi-client-axios?label=client)](https://www.npmjs.com/package/openapi-backend) 19 | [![GitHub stars](https://img.shields.io/github/stars/openapistack/docs?label=github%20stars)](https://github.com/openapistack/docs) 20 | 21 |
22 | 23 | **openapi-stack** is a collection of open source libraries and tools for full stack software development using [OpenAPI specification](https://www.openapis.org/). 24 | 25 | The goal is to unlock great developer experience and full stack type safety for software teams using REST; inspired by tools like [GraphQL](https://graphql.org/) and [tRPC](https://trpc.io). 26 | 27 | 28 |
29 | How does openapi-stack compare to GraphQL? 30 | 31 | [*GraphQL*](https://graphql.org/) is a query language for APIs developed by Facebook. It gives API clients full control over the data they query, making it extremely flexible and efficient for client-centric use cases. 32 | 33 | Similar to [OpenAPI specification](https://www.openapis.org/), GraphQL APIs define a strongly typed schema for the data and mutations they support which makes them discoverable and intuitive to develop against. 34 | 35 | OpenAPI stack achieves the same type safety and great developer experience by using the OpenAPI specification as a single source of truth for the API contract, used to generate types for both client and server side and utilising it for routing and validation during runtime. 36 | 37 | Both GraphQL and openapi-stack encourage an [API First](/docs/api-first/) approach where the API contract is treated as a first class citizen in software design instead of treating it as merely documentation. 38 | 39 | While REST APIs don't generally provide the same level of control to clients as GraphQL, many times this could be seen as a benefit especially in scenarios where strict control over data access and operations is crucial. 40 | 41 | Many organizations choose REST over GraphQL due to more established conventions, simplicity, and the ability to leverage standard HTTP features directly. Widespread knowledge around REST contribute to its choice among organizations looking for a tried-and-tested approach to building APIs. 42 |
43 | 44 |
45 | How does openapi-stack compare to tRPC? 46 | 47 | [tRPC](https://trpc.io/) is a *Remote Procedure Call* (RPC) library for Typescript to build and consume typesafe APIs. 48 | 49 | Designed for full-stack Typescript applications, tRPC allows direct sharing of types between both the client and server, without relying on code generation. 50 | 51 | Unlike GraphQL and REST, tRPC doesn't expose a standard machine-readable API schema to be consumed by clients, instead taking a more straightforward approach of exposing endpoints or *procedures*, essentially [*"just functions"*](https://trpc.io/docs/concepts#its-just-functions) invoked by the client to the server. 52 | 53 | OpenAPI stack achieves type safety using a similar workflow to tRPC's procedures with [*OpenAPI operations*](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operation-object), also avoiding code generation by only generating types from OpenAPI spec and using the machine readable contract in the runtime for routing and validation. 54 | 55 | While the lightweight tRPC approach is optimal for teams just looking to build full stack applications, teams looking to build robust APIs are better served by the API design first approach of openapi-stack or GraphQL. 56 | 57 |
58 | 59 | ## Benefits 60 | 61 | 1. 🚀 **No code generation.** Write your own code the way you like it. Only generate types from OpenAPI spec if you want. 62 | 1. 🤝 **Single source of truth for your API contract.** No more manually updating your OpenAPI specs to keep up with your backend code. Ensure your API docs and SDKs stay up to date by using the spec in runtime to route and validate. 63 | 1. 🧙‍♂️ **Type safety and validation.** Build your product faster and with a better developer experience using strongly typed Typescript and code autocomplete both in the server and client side. 64 | 1. ❤️ **Testing & Collaboration.** Leverage API mocks to make testing and development easier and iterate fast on your API design as you build your app's interface. Being blocked by the backend team is a thing of the past! 65 | 66 | ## Backend 67 | 68 | Build, Validate, Route, Authenticate, and Mock your backend using the [openapi-backend](https://github.com/openapistack/openapi-backend) library. 69 | 70 | [Quickstart](/docs/openapi-backend/intro) - [NPM](https://www.npmjs.com/package/openapi-backend) 71 | 72 | ## Client 73 | 74 | Easily consume your API using the typesafe [openapi-client-axios](https://github.com/openapistack/openapi-client-axios) library. 75 | 76 | [Quickstart](/docs/openapi-client-axios/intro) - [NPM](https://www.npmjs.com/package/openapi-client-axios) 77 | 78 | ## CLI 79 | 80 | Generate types, design and test your API using the [openapicmd](https://github.com/openapistack/openapicmd) command line tool. 81 | 82 | [Quickstart](/docs/openapicmd/intro) - [NPM](https://www.npmjs.com/package/openapicmd) 83 | 84 | ## Features 85 | 86 | - [x] Battle-tested in production. High test coverage. 87 | - [x] ️No code generation – we only generate types 88 | - [x] Built with TypeScript, types included with full autocomplete support 89 | - [x] Framework agnostic – works with your stack 90 | - [x] Lightweight - small frontend bundle + optimized for serverless cold starts 91 | - [x] OpenAPI 3.x support 92 | - [x] [Samples](/docs/examples/boilerplate/) included 93 | 94 | ## Star History 95 | 96 | [![Star History Chart](https://api.star-history.com/svg?repos=openapistack/openapi-backend,openapistack/openapi-client-axios,openapistack/openapicmd,openapistack/docs&type=Date)](https://star-history.com/#openapistack/openapi-backend&openapistack/openapi-client-axios&openapistack/openapicmd&openapistack/docs&Date) 97 | -------------------------------------------------------------------------------- /docs/openapi-backend/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Backend", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /docs/openapi-backend/examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Framework Examples 6 | 7 | OpenAPI backend is framework agnostic, which means you can use it with pretty much any javascript backend framework and hosting you're familiar with. 8 | 9 | Full, tested examples can be found the openapi-backend GitHub repository: [https://github.com/openapistack/openapi-backend/tree/main/examples/](https://github.com/openapistack/openapi-backend/tree/main/examples/) 10 | 11 | ### Express 12 | 13 | ```javascript 14 | import express from "express"; 15 | 16 | const app = express(); 17 | app.use(express.json()); 18 | app.use((req, res) => api.handleRequest(req, req, res)); 19 | app.listen(9000); 20 | ``` 21 | 22 | [See full Express example](https://github.com/openapistack/openapi-backend/tree/main/examples/express) 23 | 24 | [See full Express TypeScript example](https://github.com/openapistack/openapi-backend/tree/main/examples/express-typescript) 25 | 26 | ### AWS Serverless (Lambda) 27 | 28 | ```javascript 29 | // API Gateway Proxy handler 30 | module.exports.handler = (event, context) => 31 | api.handleRequest( 32 | { 33 | method: event.httpMethod, 34 | path: event.path, 35 | query: event.queryStringParameters, 36 | body: event.body, 37 | headers: event.headers, 38 | }, 39 | event, 40 | context 41 | ); 42 | ``` 43 | 44 | [See full AWS SAM example](https://github.com/openapistack/openapi-backend/tree/main/examples/aws-sam) 45 | 46 | [See full AWS CDK example](https://github.com/openapistack/openapi-backend/tree/main/examples/aws-cdk) 47 | 48 | [See full SST example](https://github.com/openapistack/openapi-backend/tree/main/examples/aws-sst) 49 | 50 | [See full Serverless Framework example](https://github.com/openapistack/openapi-backend/tree/main/examples/serverless-framework) 51 | 52 | ### Azure Function 53 | 54 | ```javascript 55 | module.exports = (context, req) => 56 | api.handleRequest( 57 | { 58 | method: req.method, 59 | path: req.params.path, 60 | query: req.query, 61 | body: req.body, 62 | headers: req.headers, 63 | }, 64 | context, 65 | req 66 | ); 67 | ``` 68 | 69 | [See full Azure Function example](https://github.com/openapistack/openapi-backend/tree/main/examples/azure-function) 70 | 71 | ### Fastify 72 | 73 | ```ts 74 | import fastify from "fastify"; 75 | 76 | fastify.route({ 77 | method: ["GET", "POST", "PUT", "PATCH", "DELETE"], 78 | url: "/*", 79 | handler: async (request, reply) => 80 | api.handleRequest( 81 | { 82 | method: request.method, 83 | path: request.url, 84 | body: request.body, 85 | query: request.query, 86 | headers: request.headers, 87 | }, 88 | request, 89 | reply 90 | ), 91 | }); 92 | fastify.listen(); 93 | ``` 94 | 95 | [See full Fastify example](https://github.com/openapistack/openapi-backend/tree/main/examples/fastify) 96 | 97 | 98 | ### Koa 99 | 100 | ```javascript 101 | import Koa from "koa"; 102 | import bodyparser from "koa-bodyparser"; 103 | 104 | const app = new Koa(); 105 | 106 | app.use(bodyparser()); 107 | app.use((ctx) => api.handleRequest(ctx.request, ctx)); 108 | app.listen(9000); 109 | ``` 110 | 111 | [See full Koa example](https://github.com/openapistack/openapi-backend/tree/main/examples/koa) 112 | 113 | ### Hapi 114 | 115 | ```javascript 116 | import Hapi from "@hapi/hapi"; 117 | 118 | const server = new Hapi.Server({ host: "0.0.0.0", port: 9000 }); 119 | server.route({ 120 | method: ["GET", "POST", "PUT", "PATCH", "DELETE"], 121 | path: "/{path*}", 122 | handler: (req, h) => 123 | api.handleRequest( 124 | { 125 | method: req.method, 126 | path: req.path, 127 | body: req.payload, 128 | query: req.query, 129 | headers: req.headers, 130 | }, 131 | req, 132 | h 133 | ), 134 | }); 135 | server.start(); 136 | ``` 137 | 138 | [See full Hapi example](https://github.com/openapistack/openapi-backend/tree/main/examples/hapi-typescript) 139 | 140 | ## More Examples 141 | 142 | A full list of openapi-stack boilerplate projects available here: [openapistack.co/docs/examples/boilerplate/](/docs/examples/boilerplate/) 143 | -------------------------------------------------------------------------------- /docs/openapi-backend/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Quick Start: Backend" 3 | hide_title: true 4 | sidebar_position: 1 5 | --- 6 | 7 |
8 | openapi-backend logo 9 |

10 | openapi-backend 11 | GitHub 12 |

13 | 14 | [![CI](https://github.com/openapistack/openapi-backend/workflows/CI/badge.svg)](https://github.com/openapistack/openapi-backend/actions?query=workflow%3ACI) 15 | [![npm version](https://img.shields.io/npm/v/openapi-backend.svg)](https://www.npmjs.com/package/openapi-backend) 16 | [![npm downloads](https://img.shields.io/npm/dw/openapi-backend)](https://www.npmjs.com/package/openapi-backend) 17 | [![GitHub stars](https://img.shields.io/github/stars/openapistack/openapi-backend)](https://github.com/openapistack/openapi-backend) 18 | [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/openapistack/openapi-backend/blob/master/LICENSE) 19 | [![Buy me a coffee](https://img.shields.io/badge/donate-buy%20me%20a%20coffee-orange)](https://buymeacoff.ee/anttiviljami) 20 | 21 |

Build, Validate, Route, Authenticate, and Mock using OpenAPI definitions.

22 | 23 |

OpenAPI Backend is a Framework-agnostic middleware tool for building beautiful APIs with OpenAPI Specification.

24 |
25 | 26 | ## Features 27 | 28 | - [x] Build APIs by describing them in [OpenAPI specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md) 29 | - [x] Register handlers for [operationIds](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#fixed-fields-8) 30 | to route requests in your favourite Node.js backend 31 | - [x] Use [JSON Schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types) to validate 32 | API requests and/or responses. OpenAPI Backend uses the [AJV](https://ajv.js.org/) library under the hood for performant validation 33 | - [x] Register Auth / Security Handlers for [OpenAPI Security Schemes](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securitySchemeObject) 34 | to authorize API requests 35 | - [x] Auto-mock API responses using [OpenAPI examples objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#example-object) 36 | or [JSON Schema definitions](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schema-object) 37 | - [x] Built with TypeScript, types included 38 | - [x] Optimised runtime routing and validation. **No generated code!** 39 | - [x] OpenAPI 3.1 support 40 | 41 | ## Quick Start 42 | 43 | The easiest way to get started with OpenAPI Backend is to check out one of the 44 | [examples](/docs/openapi-backend/examples). 45 | 46 | ``` 47 | npm install --save openapi-backend 48 | ``` 49 | 50 | ```javascript 51 | import OpenAPIBackend from "openapi-backend"; 52 | 53 | // create api with your definition file or object 54 | const api = new OpenAPIBackend({ definition: "./petstore.yml" }); 55 | 56 | // register your framework specific request handlers here 57 | api.register({ 58 | getPets: (c, req, res) => res.status(200).json({ result: "ok" }), 59 | getPetById: (c, req, res) => res.status(200).json({ result: "ok" }), 60 | notFound: (c, req, res) => res.status(404).json({ err: "not found" }), 61 | validationFail: (c, req, res) => 62 | res.status(400).json({ err: c.validation.errors }), 63 | }); 64 | 65 | // initalize the backend 66 | api.init(); 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/openapi-backend/mocking.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | 5 | # Mocking API responses 6 | 7 | Mocking APIs just got really easy with OpenAPI Backend! Register a [`notImplemented`](/docs/openapi-backend/api#notimplemented-handler) 8 | handler and use [`mockResponseForOperation()`](/docs/openapi-backend/api##mockresponseforoperationoperationid-opts) 9 | to generate mock responses for operations with no custom handlers specified yet: 10 | 11 | ```javascript 12 | api.register("notImplemented", (c, req, res) => { 13 | const { status, mock } = c.api.mockResponseForOperation( 14 | c.operation.operationId 15 | ); 16 | return res.status(status).json(mock); 17 | }); 18 | ``` 19 | 20 | OpenAPI Backend supports mocking responses using both OpenAPI example objects and JSON Schema: 21 | 22 | ```yaml 23 | paths: 24 | "/pets": 25 | get: 26 | operationId: getPets 27 | summary: List pets 28 | responses: 29 | 200: 30 | $ref: "#/components/responses/PetListWithExample" 31 | "/pets/{id}": 32 | get: 33 | operationId: getPetById 34 | summary: Get pet by its id 35 | responses: 36 | 200: 37 | $ref: "#/components/responses/PetResponseWithSchema" 38 | components: 39 | responses: 40 | PetListWithExample: 41 | description: List of pets 42 | content: 43 | "application/json": 44 | example: 45 | - id: 1 46 | name: Garfield 47 | - id: 2 48 | name: Odie 49 | PetResponseWithSchema: 50 | description: A single pet 51 | content: 52 | "application/json": 53 | schema: 54 | type: object 55 | properties: 56 | id: 57 | type: integer 58 | minimum: 1 59 | name: 60 | type: string 61 | example: Garfield 62 | ``` 63 | 64 | The example above will yield: 65 | 66 | ```javascript 67 | api.mockResponseForOperation("getPets"); // => { status: 200, mock: [{ id: 1, name: 'Garfield' }, { id: 2, name: 'Odie' }]} 68 | api.mockResponseForOperation("getPetById"); // => { status: 200, mock: { id: 1, name: 'Garfield' }} 69 | ``` 70 | 71 | [See full Mock API example on Express](https://github.com/openapistack/openapi-backend/tree/main/examples/express-ts-mock) 72 | -------------------------------------------------------------------------------- /docs/openapi-backend/operation-handlers.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Operation Handlers 6 | 7 | Handlers are controllers for operations described in your OpenAPI document. They are registered for each [`operationId`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#fixed-fields-8) found in the OpenAPI definitions. 8 | 9 | ```ts 10 | import type { Context } from "openapi-backend"; 11 | import type { Request, Response } from "express"; 12 | 13 | async function getPetByIdHandler( 14 | c: Context<{ id: string }>, 15 | _req: Request, 16 | res: Response 17 | ) { 18 | const id = c.request.params.id; 19 | const pet = await pets.getPetById(id); 20 | return res.status(200).json({ result: pet }); 21 | } 22 | 23 | api.register("getPetById", getPetByIdHandler); 24 | // or 25 | api.register({ 26 | getPetById: getPetByIdHandler, 27 | }); 28 | ``` 29 | 30 | Operation handlers are passed a special [Context object](/docs/openapi-backend/api#context-object) as the first argument, which contains the parsed request, the matched API operation and input validation results. 31 | 32 | Arguments 2 and higher are passed through from [`handleRequest`](/docs/openapi-backend/api#handlerequestreq-handlerargs) 33 | 34 | ### validationFail Handler 35 | 36 | The `validationFail` handler gets called by `.handleRequest()` if the input validation fails for a request. 37 | 38 | HINT: You should probably return a 400 status code from this handler. 39 | 40 | Example handler: 41 | 42 | ```javascript 43 | function validationFailHandler(c, req, res) { 44 | return res.status(400).json({ status: 400, err: c.validation.errors }); 45 | } 46 | api.register("validationFail", validationFailHandler); 47 | ``` 48 | 49 | ### notFound Handler 50 | 51 | The `notFound` handler gets called by `.handleRequest()` if the path doesn't 52 | match an operation in the API definitions. 53 | 54 | HINT: You should probably return a 404 status code from this handler. 55 | 56 | Example handler: 57 | 58 | ```javascript 59 | function notFound(c, req, res) { 60 | return res.status(404).json({ status: 404, err: "Not found" }); 61 | } 62 | api.register("notFound", notFound); 63 | ``` 64 | 65 | ### methodNotAllowed Handler 66 | 67 | The `methodNotAllowed` handler gets called by `.handleRequest()` if request 68 | method does not match any operations for the path. 69 | 70 | If this handler isn't registered, the [notFound Handler](#notfound-handler) will be used instead. 71 | 72 | HINT: You should probably return a 405 status code from this handler. 73 | 74 | Example handler: 75 | 76 | ```javascript 77 | function methodNotAllowed(c, req, res) { 78 | return res.status(405).json({ status: 405, err: "Method not allowed" }); 79 | } 80 | api.register("methodNotAllowed", methodNotAllowed); 81 | ``` 82 | 83 | ### notImplemented Handler 84 | 85 | The `notImplemented` handler gets called by `.handleRequest()` if no other Operation Handler has been registered for 86 | the matched operation. 87 | 88 | HINT: You can either mock the response or return a 501 status code. 89 | 90 | Example handler: 91 | 92 | ```javascript 93 | function notImplementedHandler(c, req, res) { 94 | return res 95 | .status(404) 96 | .json({ status: 501, err: "No handler registered for operation" }); 97 | } 98 | api.register("notImplemented", notImplementedHandler); 99 | ``` 100 | 101 | ### unauthorizedHandler Handler 102 | 103 | The `unauthorizedHandler` handler gets called by `.handleRequest()` if security 104 | requirements are not met after checking [Security Requirements](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securityRequirementObject) 105 | and calling their [Security Handlers](#security-handlers). 106 | 107 | HINT: You should probably return a 401 or 403 code from this handler and 108 | instruct the client to authenticate. 109 | 110 | Example handler: 111 | 112 | ```javascript 113 | function unauthorizedHandler(c, req, res) { 114 | return res 115 | .status(401) 116 | .json({ status: 401, err: "Please authenticate first" }); 117 | } 118 | api.register("unauthorizedHandler", unauthorizedHandler); 119 | ``` 120 | 121 | If no `unauthorizedHandler` is registered, the Security Handlers will still be 122 | called and their output and the authorization status for the request can be 123 | checked in operation handlers via the [`context.security` property](#context-object). 124 | 125 | ### postResponseHandler Handler 126 | 127 | The `postResponseHandler` handler gets called by `.handleRequest()` after resolving the response handler. 128 | 129 | The return value of the response handler will be passed in the context object `response` property. 130 | 131 | HINT: You can use the postResponseHandler to validate API responses against your response schema 132 | 133 | Example handler: 134 | 135 | ```javascript 136 | function postResponseHandler(c, req, res) { 137 | const valid = c.api.validateResponse(c.response, c.operation); 138 | if (valid.errors) { 139 | // response validation failed 140 | return res.status(502).json({ status: 502, err: valid.errors }); 141 | } 142 | return res.status(200).json(c.response); 143 | } 144 | api.register("postResponseHandler", postResponseHandler); 145 | ``` 146 | -------------------------------------------------------------------------------- /docs/openapi-backend/request-validation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Request validation 6 | 7 | All you need to enable request validation is to register a [`validationFail`](/docs/openapi-backend/api#validationfail-handler) handler. 8 | 9 | ```ts 10 | function validationFailHandler(c: Context, _req: Request, res: Response) { 11 | return res.status(400).json({ status: 400, err: c.validation.errors }); 12 | } 13 | api.register("validationFail", validationFailHandler); 14 | ``` 15 | 16 | Once registered, this handler gets called if any JSON Schemas in either operation parameters (in: path, query, header, cookie) or requestPayload don't match the request. 17 | 18 | The context object `c` gets a `validation` property with the [validation result](/docs/openapi-backend/api#validationresult-object). 19 | 20 | ## Extended Formats 21 | 22 | To add validation for JSON Schema formats like `email`, `uri`, `date-time`, `uuid` you can use the [`customizeAjv`](/docs/openapi-backend/api/#parameter-optscustomizeajvoriginalajv-ajvopts-validationcontext) option when creating your OpenAPIIBackend instance to extend Ajv. 23 | 24 | ```ts 25 | import addFormats from 'ajv-formats'; 26 | 27 | const api = new OpenAPIBackend({ 28 | definition, 29 | customizeAjv: (ajv) => { 30 | addFormats(ajv, { mode: 'fast', formats: ['email', 'uri', 'date-time', 'uuid'] }); 31 | 32 | return ajv; 33 | }, 34 | }); 35 | ``` 36 | 37 | You can simply opt to add all formats with `addFormats(ajv)`. Warning: this may slow down the initialisation of openapi-backend, and is not recommended in FaaS environments. 38 | 39 | See [ajv-formats documentation](https://ajv.js.org/packages/ajv-formats.html) for more configuration options. 40 | -------------------------------------------------------------------------------- /docs/openapi-backend/response-validation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Response validation 6 | 7 | While not recommended for production APIs, to enable response validation for your handlers, you can register a [`postResponseHandler`](/docs/openapi-backend/api#postresponsehandler-handler) 8 | to add a response validation step using [`validateResponse`](/docs/openapi-backend/api#validateresponseres-operation). 9 | 10 | ```ts 11 | api.register({ 12 | getPets: (c) => { 13 | // when a postResponseHandler is registered, your operation handlers' return value gets passed to context.response 14 | return [{ id: 1, name: "Garfield" }]; 15 | }, 16 | postResponseHandler: (c: Context, _req: Request, res: Response) => { 17 | const valid = c.api.validateResponse(c.response, c.operation); 18 | if (valid.errors) { 19 | // response validation failed 20 | return res.status(502).json({ status: 502, err: valid.errors }); 21 | } 22 | return res.status(200).json(c.response); 23 | }, 24 | }); 25 | ``` 26 | 27 | It's also possible to validate the response headers using [`validateResponseHeaders`](/docs/openapi-backend/api#validateresponseheadersheaders-operation-opts). 28 | 29 | ```javascript 30 | api.register({ 31 | getPets: (c) => { 32 | // when a postResponseHandler is registered, your operation handlers' return value gets passed to context.response 33 | return [{ id: 1, name: "Garfield" }]; 34 | }, 35 | postResponseHandler: (c, req, res) => { 36 | const valid = c.api.validateResponseHeaders(res.headers, c.operation, { 37 | statusCode: res.statusCode, 38 | setMatchType: "exact", 39 | }); 40 | if (valid.errors) { 41 | // response validation failed 42 | return res.status(502).json({ status: 502, err: valid.errors }); 43 | } 44 | return res.status(200).json(c.response); 45 | }, 46 | }); 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/openapi-backend/security-handlers.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 8 3 | --- 4 | 5 | # Auth with Security Handlers 6 | 7 | :::tip 8 | 9 | Check out [boilerplate projects](/docs/examples/boilerplate/) for working examples of authorization with security handlers. ([JWT](https://github.com/openapistack/openapi-backend/tree/main/examples/express-jwt-auth), [API Key](https://github.com/openapistack/openapi-backend/tree/main/examples/express-apikey-auth)) 10 | 11 | ::: 12 | 13 | When your OpenAPI document contains [Security Schemes](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securitySchemeObject) 14 | you can register security handlers to handle authorization for your API: 15 | 16 | ```yaml 17 | components: 18 | securitySchemes: 19 | - ApiKey: 20 | type: apiKey 21 | in: header 22 | name: x-api-key 23 | security: 24 | - ApiKey: [] 25 | ``` 26 | 27 | ```javascript 28 | api.registerSecurityHandler("ApiKey", (c) => { 29 | const authorized = 30 | c.request.headers["x-api-key"] === "SuperSecretPassword123"; 31 | // truthy return values are interpreted as auth success 32 | // you can also add any auth information to the return value 33 | return authorized; 34 | }); 35 | ``` 36 | 37 | The authorization status and return values of each security handler can be 38 | accessed via the [Context Object](/docs/openapi-backend/api#context-object) 39 | 40 | You can also register an [`unauthorizedHandler`](/docs/openapi-backend/api#unauthorizedhandler-handler) 41 | to handle unauthorized requests. 42 | 43 | ```javascript 44 | api.register("unauthorizedHandler", (c, req, res) => { 45 | return res.status(401).json({ err: "unauthorized" }); 46 | }); 47 | ``` 48 | 49 | See examples using security handlers: 50 | 51 | - [API Key auth (express)](https://github.com/openapistack/openapi-backend/tree/main/examples/express-apikey-auth) 52 | - [JWT auth (express)](https://github.com/openapistack/openapi-backend/tree/main/examples/express-jwt-auth) 53 | 54 | ## Security Handlers 55 | 56 | Example handler for JWT auth: 57 | 58 | ```javascript 59 | const jwt = require("jsonwebtoken"); 60 | 61 | function jwtHandler(c, req, res) { 62 | const authHeader = c.request.headers["authorization"]; 63 | if (!authHeader) { 64 | throw new Error("Missing authorization header"); 65 | } 66 | const token = authHeader.replace("Bearer ", ""); 67 | return jwt.verify(token, "secret"); 68 | } 69 | 70 | api.registerSecurityHandler("jwt", jwtHandler); 71 | ``` 72 | 73 | The first argument of the handler is the [Context object](/docs/openapi-backend/api#context-object) and rest are passed from `.handleRequest()` 74 | arguments, starting from the second one. 75 | 76 | The return value of each security handler is added as a key-value mapping to 77 | `security` property of the [Context object](/docs/openapi-backend/api#context-object). 78 | 79 | Truthy return values from security handlers are generally interpreted as auth 80 | success, unless the return value is an object containing an `error` property. 81 | 82 | All falsy return values are interpreted as auth fail. 83 | 84 | Throwing an error from the security handler also gets interpreted as auth fail. The error is available in `context.security[name].error`. 85 | -------------------------------------------------------------------------------- /docs/openapi-backend/typescript.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 9 3 | title: TypeScript 4 | --- 5 | 6 | # openapi-backend with TypeScript 7 | 8 | OpenAPI Backend is entirely built with typescript and supports using types in operation handlers. 9 | 10 | The [`openapi typegen`](/docs/openapicmd/typegen) command can be used to generate types to for use on the backend side using the `--backend` option. 11 | 12 | :::tip 13 | 14 | You can set up a script in package.json to easily update types when the openapi spec is changed. 15 | 16 | ```json 17 | { 18 | "scripts": { 19 | "openapi": "openapi typegen --backend ./openapi.yaml > src/types/openapi.d.ts" 20 | } 21 | } 22 | ``` 23 | 24 | ::: 25 | 26 | `openapi typegen` supports both local and remote files: 27 | 28 | ``` 29 | npx openapicmd typegen --backend ./openapi.yaml > src/types/openapi.d.ts # local file 30 | npx openapicmd typegen --backend https://petstore3.swagger.io/api/v3/openapi.json > src/types/openapi.d.ts # remote url 31 | ``` 32 | 33 | ## Importing generated types 34 | 35 | You can directly import types defined as schemas in your openapi spec as Typescript types: 36 | 37 | ```ts 38 | import type { Pet, User } from "./openapi.d.ts"; 39 | 40 | const myPet: Pet = { 41 | id: 1, 42 | name: "My Pet", 43 | tag: "My Tag", 44 | }; 45 | 46 | const myUser: User = { 47 | id: 1, 48 | name: "My User", 49 | }; 50 | ``` 51 | 52 | ## Typesafe Operation Handlers 53 | 54 | For type safety in API handlers, annotate your operation handlers with the `OperationHandler` generic type: 55 | 56 | ```ts 57 | import type { OperationHandler, OperationResponse } from "./openapi.d.ts"; 58 | import type { Request, Response } from "express"; 59 | 60 | const updatePetHandler: OperationHandler<"updatePet"> = async ( 61 | c, 62 | _req: Request, 63 | res: Response 64 | ) => { 65 | const petId = c.request.params.petId; // string 66 | const requestBody = c.request.requestBody; // Pet 67 | 68 | const updatedPet = await db.updatePet(petId, requestBody); 69 | 70 | const response: OperationResponse<"updatePet"> = { 71 | ...updatedPet, 72 | }; 73 | 74 | return res.status(200).json(response); 75 | }; 76 | ``` 77 | 78 | You can also create a typed response util function using the `HandlerResponse` generic type to make sure the response is typed correctly: 79 | 80 | ```ts 81 | import type { HandlerResponse } from "./openapi.d.ts"; 82 | import type { Response } from "express"; 83 | 84 | export const replyJSON = ( 85 | json: T, 86 | res: Response, 87 | statusCode: number = 200 88 | ): HandlerResponse => { 89 | return res.status(statusCode).json(json); 90 | }; 91 | ``` 92 | 93 | Usage: 94 | 95 | ```ts 96 | import { replyJSON } from "./utils"; 97 | 98 | const getPetHandler: OperationHandler<"getPet"> = async (c) => { 99 | const petId = c.request.params.petId; // string 100 | 101 | const result = await db.getPetById(petId); 102 | 103 | return replyJSON(result, res); 104 | }; 105 | ``` 106 | 107 | ## Example type file 108 | 109 | Here's a full example of a generated type file: 110 | 111 | ```ts 112 | // openapi.d.ts 113 | 114 | import type { Context, UnknownParams } from "openapi-backend"; 115 | 116 | declare namespace Components { 117 | namespace RequestBodies { 118 | export type PetPayload = Schemas.PetPayload; 119 | } 120 | namespace Schemas { 121 | /** 122 | * PetId 123 | * Unique identifier for pet in database 124 | * example: 125 | * 1 126 | */ 127 | export type PetId = number; 128 | export interface PetPayload { 129 | /** 130 | * PetName 131 | * Name of the pet 132 | * example: 133 | * Garfield 134 | */ 135 | name: string; 136 | } 137 | /** 138 | * QueryLimit 139 | * Number of items to return 140 | * example: 141 | * 25 142 | */ 143 | export type QueryLimit = number; 144 | /** 145 | * QueryOffset 146 | * Starting offset for returning items 147 | * example: 148 | * 0 149 | */ 150 | export type QueryOffset = number; 151 | } 152 | } 153 | declare namespace Paths { 154 | namespace CreatePet { 155 | export type RequestBody = Components.RequestBodies.PetPayload; 156 | namespace Responses { 157 | export interface $201 {} 158 | } 159 | } 160 | namespace DeletePetById { 161 | namespace Parameters { 162 | export type Id = 163 | /** 164 | * PetId 165 | * Unique identifier for pet in database 166 | * example: 167 | * 1 168 | */ 169 | Components.Schemas.PetId; 170 | } 171 | export interface PathParameters { 172 | id: Parameters.Id; 173 | } 174 | namespace Responses { 175 | export interface $200 {} 176 | export interface $404 {} 177 | } 178 | } 179 | namespace GetOwnerByPetId { 180 | namespace Parameters { 181 | export type Id = 182 | /** 183 | * PetId 184 | * Unique identifier for pet in database 185 | * example: 186 | * 1 187 | */ 188 | Components.Schemas.PetId; 189 | } 190 | export interface PathParameters { 191 | id: Parameters.Id; 192 | } 193 | namespace Responses { 194 | export interface $200 {} 195 | export interface $404 {} 196 | } 197 | } 198 | namespace GetPetById { 199 | namespace Parameters { 200 | export type Id = 201 | /** 202 | * PetId 203 | * Unique identifier for pet in database 204 | * example: 205 | * 1 206 | */ 207 | Components.Schemas.PetId; 208 | } 209 | export interface PathParameters { 210 | id: Parameters.Id; 211 | } 212 | namespace Responses { 213 | export interface $200 {} 214 | export interface $404 {} 215 | } 216 | } 217 | namespace GetPetOwner { 218 | namespace Parameters { 219 | export type OwnerId = 220 | /** 221 | * PetId 222 | * Unique identifier for pet in database 223 | * example: 224 | * 1 225 | */ 226 | Components.Schemas.PetId; 227 | export type PetId = 228 | /** 229 | * PetId 230 | * Unique identifier for pet in database 231 | * example: 232 | * 1 233 | */ 234 | Components.Schemas.PetId; 235 | } 236 | export interface PathParameters { 237 | petId: Parameters.PetId; 238 | ownerId: Parameters.OwnerId; 239 | } 240 | namespace Responses { 241 | export interface $200 {} 242 | export interface $404 {} 243 | } 244 | } 245 | namespace GetPets { 246 | namespace Parameters { 247 | export type Limit = 248 | /** 249 | * QueryLimit 250 | * Number of items to return 251 | * example: 252 | * 25 253 | */ 254 | Components.Schemas.QueryLimit; 255 | export type Offset = 256 | /** 257 | * QueryOffset 258 | * Starting offset for returning items 259 | * example: 260 | * 0 261 | */ 262 | Components.Schemas.QueryOffset; 263 | } 264 | export interface QueryParameters { 265 | limit?: Parameters.Limit; 266 | offset?: Parameters.Offset; 267 | } 268 | namespace Responses { 269 | export interface $200 {} 270 | } 271 | } 272 | namespace GetPetsMeta { 273 | namespace Responses { 274 | export interface $200 {} 275 | } 276 | } 277 | namespace GetPetsRelative { 278 | namespace Responses { 279 | export interface $200 {} 280 | } 281 | } 282 | namespace ReplacePetById { 283 | namespace Parameters { 284 | export type Id = 285 | /** 286 | * PetId 287 | * Unique identifier for pet in database 288 | * example: 289 | * 1 290 | */ 291 | Components.Schemas.PetId; 292 | } 293 | export interface PathParameters { 294 | id: Parameters.Id; 295 | } 296 | export type RequestBody = Components.RequestBodies.PetPayload; 297 | namespace Responses { 298 | export interface $200 {} 299 | export interface $404 {} 300 | } 301 | } 302 | namespace UpdatePetById { 303 | namespace Parameters { 304 | export type Id = 305 | /** 306 | * PetId 307 | * Unique identifier for pet in database 308 | * example: 309 | * 1 310 | */ 311 | Components.Schemas.PetId; 312 | } 313 | export interface PathParameters { 314 | id: Parameters.Id; 315 | } 316 | export type RequestBody = Components.RequestBodies.PetPayload; 317 | namespace Responses { 318 | export interface $200 {} 319 | export interface $404 {} 320 | } 321 | } 322 | } 323 | 324 | export interface Operations { 325 | /** 326 | * GET /pets 327 | */ 328 | ["getPets"]: { 329 | requestBody: any; 330 | params: UnknownParams; 331 | query: Paths.GetPets.QueryParameters; 332 | headers: UnknownParams; 333 | cookies: UnknownParams; 334 | context: Context< 335 | any, 336 | UnknownParams, 337 | Paths.GetPets.QueryParameters, 338 | UnknownParams, 339 | UnknownParams 340 | >; 341 | response: Paths.GetPets.Responses.$200; 342 | }; 343 | /** 344 | * POST /pets 345 | */ 346 | ["createPet"]: { 347 | requestBody: Paths.CreatePet.RequestBody; 348 | params: UnknownParams; 349 | query: UnknownParams; 350 | headers: UnknownParams; 351 | cookies: UnknownParams; 352 | context: Context< 353 | Paths.CreatePet.RequestBody, 354 | UnknownParams, 355 | UnknownParams, 356 | UnknownParams, 357 | UnknownParams 358 | >; 359 | response: Paths.CreatePet.Responses.$201; 360 | }; 361 | /** 362 | * GET /pets/{id} 363 | */ 364 | ["getPetById"]: { 365 | requestBody: any; 366 | params: Paths.GetPetById.PathParameters; 367 | query: UnknownParams; 368 | headers: UnknownParams; 369 | cookies: UnknownParams; 370 | context: Context< 371 | any, 372 | Paths.GetPetById.PathParameters, 373 | UnknownParams, 374 | UnknownParams, 375 | UnknownParams 376 | >; 377 | response: Paths.GetPetById.Responses.$200 | Paths.GetPetById.Responses.$404; 378 | }; 379 | /** 380 | * PUT /pets/{id} 381 | */ 382 | ["replacePetById"]: { 383 | requestBody: Paths.ReplacePetById.RequestBody; 384 | params: Paths.ReplacePetById.PathParameters; 385 | query: UnknownParams; 386 | headers: UnknownParams; 387 | cookies: UnknownParams; 388 | context: Context< 389 | Paths.ReplacePetById.RequestBody, 390 | Paths.ReplacePetById.PathParameters, 391 | UnknownParams, 392 | UnknownParams, 393 | UnknownParams 394 | >; 395 | response: 396 | | Paths.ReplacePetById.Responses.$200 397 | | Paths.ReplacePetById.Responses.$404; 398 | }; 399 | /** 400 | * PATCH /pets/{id} 401 | */ 402 | ["updatePetById"]: { 403 | requestBody: Paths.UpdatePetById.RequestBody; 404 | params: Paths.UpdatePetById.PathParameters; 405 | query: UnknownParams; 406 | headers: UnknownParams; 407 | cookies: UnknownParams; 408 | context: Context< 409 | Paths.UpdatePetById.RequestBody, 410 | Paths.UpdatePetById.PathParameters, 411 | UnknownParams, 412 | UnknownParams, 413 | UnknownParams 414 | >; 415 | response: 416 | | Paths.UpdatePetById.Responses.$200 417 | | Paths.UpdatePetById.Responses.$404; 418 | }; 419 | /** 420 | * DELETE /pets/{id} 421 | */ 422 | ["deletePetById"]: { 423 | requestBody: any; 424 | params: Paths.DeletePetById.PathParameters; 425 | query: UnknownParams; 426 | headers: UnknownParams; 427 | cookies: UnknownParams; 428 | context: Context< 429 | any, 430 | Paths.DeletePetById.PathParameters, 431 | UnknownParams, 432 | UnknownParams, 433 | UnknownParams 434 | >; 435 | response: 436 | | Paths.DeletePetById.Responses.$200 437 | | Paths.DeletePetById.Responses.$404; 438 | }; 439 | /** 440 | * GET /pets/{id}/owner 441 | */ 442 | ["getOwnerByPetId"]: { 443 | requestBody: any; 444 | params: Paths.GetOwnerByPetId.PathParameters; 445 | query: UnknownParams; 446 | headers: UnknownParams; 447 | cookies: UnknownParams; 448 | context: Context< 449 | any, 450 | Paths.GetOwnerByPetId.PathParameters, 451 | UnknownParams, 452 | UnknownParams, 453 | UnknownParams 454 | >; 455 | response: 456 | | Paths.GetOwnerByPetId.Responses.$200 457 | | Paths.GetOwnerByPetId.Responses.$404; 458 | }; 459 | /** 460 | * GET /pets/{petId}/owner/{ownerId} 461 | */ 462 | ["getPetOwner"]: { 463 | requestBody: any; 464 | params: Paths.GetPetOwner.PathParameters; 465 | query: UnknownParams; 466 | headers: UnknownParams; 467 | cookies: UnknownParams; 468 | context: Context< 469 | any, 470 | Paths.GetPetOwner.PathParameters, 471 | UnknownParams, 472 | UnknownParams, 473 | UnknownParams 474 | >; 475 | response: 476 | | Paths.GetPetOwner.Responses.$200 477 | | Paths.GetPetOwner.Responses.$404; 478 | }; 479 | /** 480 | * GET /pets/meta 481 | */ 482 | ["getPetsMeta"]: { 483 | requestBody: any; 484 | params: UnknownParams; 485 | query: UnknownParams; 486 | headers: UnknownParams; 487 | cookies: UnknownParams; 488 | context: Context< 489 | any, 490 | UnknownParams, 491 | UnknownParams, 492 | UnknownParams, 493 | UnknownParams 494 | >; 495 | response: Paths.GetPetsMeta.Responses.$200; 496 | }; 497 | /** 498 | * GET /pets/relative 499 | */ 500 | ["getPetsRelative"]: { 501 | requestBody: any; 502 | params: UnknownParams; 503 | query: UnknownParams; 504 | headers: UnknownParams; 505 | cookies: UnknownParams; 506 | context: Context< 507 | any, 508 | UnknownParams, 509 | UnknownParams, 510 | UnknownParams, 511 | UnknownParams 512 | >; 513 | response: Paths.GetPetsRelative.Responses.$200; 514 | }; 515 | } 516 | 517 | export type OperationContext = 518 | Operations[operationId]["context"]; 519 | export type OperationResponse = 520 | Operations[operationId]["response"]; 521 | export type HandlerResponse< 522 | ResponseBody, 523 | ResponseModel = Record 524 | > = ResponseModel & { _t?: ResponseBody }; 525 | export type OperationHandlerResponse = 526 | HandlerResponse>; 527 | export type OperationHandler< 528 | operationId extends keyof Operations, 529 | HandlerArgs extends unknown[] = unknown[] 530 | > = ( 531 | ...params: [OperationContext, ...HandlerArgs] 532 | ) => Promise>; 533 | 534 | export type PetId = Components.Schemas.PetId; 535 | export type PetPayload = Components.Schemas.PetPayload; 536 | export type QueryLimit = Components.Schemas.QueryLimit; 537 | export type QueryOffset = Components.Schemas.QueryOffset; 538 | ``` 539 | -------------------------------------------------------------------------------- /docs/openapi-backend/versioning.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide_title: true 3 | sidebar_position: 10 4 | --- 5 | 6 | # API Versioning 7 | 8 | Since OpenAPI specification already supports the `version` field in the [`info` object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#infoObject), 9 | it's easy to do URI versioning utilising multiple [`OpenAPIBackend`](#class-openapibackend) instances with different 10 | [`apiRoot`](#parameter-optsapiroot) values. 11 | 12 | Simple example: 13 | 14 | ```typescript 15 | const apiV1 = new OpenAPIBackend({ 16 | definition: "./openapi-v1.json", 17 | apiRoot: "/v1", 18 | }); 19 | 20 | const apiV2 = new OpenAPIBackend({ 21 | definition: "./openapi-v2.json", 22 | apiRoot: "/v2", 23 | }); 24 | 25 | const v1Handlers = { 26 | notFound, 27 | getPet, 28 | listPets, 29 | createPet, 30 | }; 31 | apiV1.register(v1Handlers); 32 | 33 | const v2Handlers = { 34 | ...v1Handlers, 35 | deletePet, // add new operation 36 | createPet: createPetV2, // override old handler 37 | }; 38 | apiV2.register(v2Handlers); 39 | ``` 40 | 41 | For a real world API versioning implementation with `openapi-backend`, see 42 | [ether/etherpad](https://github.com/ether/etherpad-lite/blob/39425e4e5bc4579d4b478d3b8b5e92fde55bde86/src/node/hooks/express/openapi.js) 43 | -------------------------------------------------------------------------------- /docs/openapi-client-axios/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Client", 3 | "position": 4 4 | } 5 | -------------------------------------------------------------------------------- /docs/openapi-client-axios/api.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 20 3 | --- 4 | 5 | # Reference 6 | 7 | ## Class OpenAPIClientAxios 8 | 9 | OpenAPIClientAxios is the main class of this module. However, it's entire purpose is to create an axios client instance 10 | configured to consume an API described by the OpenAPI definition. 11 | 12 | ### new OpenAPIClientAxios(opts) 13 | 14 | Creates an instance of OpenAPIClientAxios and returns it. 15 | 16 | Example: 17 | ```javascript 18 | const api = new OpenAPIClientAxios({ 19 | definition: '/openapi.json', 20 | withServer: 0, 21 | axiosConfigDefaults: { 22 | withCredentials: true, 23 | headers: { 24 | 'Cache-Control': 'no-cache', 25 | }, 26 | }, 27 | }); 28 | ``` 29 | 30 | #### Parameter: opts 31 | 32 | Constructor options 33 | 34 | #### Parameter: opts.definition 35 | 36 | The OpenAPI definition as a URL or [Document object](#document-object). 37 | 38 | To support YAML openapi files, `js-yaml` must be installed. 39 | 40 | Type: `Document | string` 41 | 42 | #### Parameter: opts.withServer 43 | 44 | The default server to use. Either by index, description or a full server object to override with. 45 | 46 | Type: `number`, `string` or [Server Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#serverObject 47 | 48 | #### Parameter: opts.axiosConfigDefaults 49 | 50 | Optional. Default axios config for the instance. Applied when instance is created. 51 | 52 | Type: [`AxiosRequestConfig`](https://github.com/axios/axios#request-config) 53 | 54 | #### Parameter: opts.transformOperationName 55 | 56 | Optional. Override the default method name resolution strategy (default: use `operationId` as method name) 57 | 58 | Type: `(operationId: string) => string` 59 | 60 | For typegen: You can pass the `transformOperationName` using the `-t` ot `--transformOperationName` command line flag. 61 | 62 | #### Parameter: opts.transformOperationMethod 63 | 64 | Optional. Transforms the returned operation method (default: do not transform) 65 | 66 | Type: `(operationMethod: UnknownOperationMethod, operationToTransform: Operation) => UnknownOperationMethod` 67 | 68 | The `operation` is also provided to the function, such that you can also conditionally transform the method. 69 | 70 | Example: 71 | 72 | ```javascript 73 | const api = new OpenAPIClientAxios({ 74 | definition: 'https://example.com/api/openapi.json', 75 | transformOperationMethod: (operationMethod, operation) => { 76 | return (params, body, config) => { 77 | // set default workspaceId for all operations 78 | params.workspaceId = '63e90965-07a7-43b3-8f5d-d2e8fa90e8d0'; 79 | return operationMethod(params, body, config); 80 | } 81 | } 82 | }); 83 | ``` 84 | 85 | ### .init() 86 | 87 | Initalizes OpenAPIClientAxios 88 | 89 | Returns a promise of the created member axios instance. 90 | 91 | 1. Parses the input definition into a JS object. If the input definition is a URL, it will be resolved 92 | 2. Dereferences the definition for use. 93 | 3. Creates the member axios instance 94 | 4. Sets `api.initialised = true` and returns the created axios instance 95 | 96 | Example: 97 | ```javascript 98 | await api.init(); 99 | ``` 100 | 101 | ### .initSync() 102 | 103 | Synchronous version of [`.init()`](#init) 104 | 105 | Initalizes OpenAPIClientAxios and creates the axios client instance. 106 | 107 | Note: Only works when the input definition is a valid OpenAPI v3 object. 108 | 109 | Example: 110 | ```javascript 111 | api.initSync(); 112 | ``` 113 | 114 | ### .getClient() 115 | 116 | Returns a promise of the member axios instance. Will run .init() if API is not initalised yet. 117 | 118 | Example: 119 | ```javascript 120 | const client = await api.getClient(); 121 | ``` 122 | 123 | ### .withServer(server) 124 | 125 | Set the default server base url to use for client. 126 | 127 | 128 | #### Parameter: server 129 | 130 | The default server to use. Either an index, description or a full server object to override with. 131 | 132 | Example: 133 | ```javascript 134 | // by index 135 | api.withServer(1); 136 | // by description property 137 | api.withServer('EU server'); 138 | // by server object (override) 139 | api.withServer({ url: 'https://example.com/api/', description: 'Eu Server' }); 140 | ``` 141 | 142 | Type: `number`, `string` or [Server Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#serverObject) 143 | 144 | ### .getBaseURL(operation?) 145 | 146 | Gets the API baseurl defined in the servers property 147 | 148 | Example: 149 | ```javascript 150 | const baseURL = api.getBaseUrl(); 151 | const baseURLForOperation = api.getBaseUrl('operationId'); 152 | ``` 153 | 154 | #### Parameter: operation 155 | 156 | Optional. The Operation object or operationId that may override the baseURL with its own or path object's servers 157 | property. 158 | 159 | Type: `Operation` or `string` (operationId) 160 | 161 | ### .getRequestConfigForOperation(operation, args) 162 | 163 | Creates a generic request config object for operation + arguments to be used for calling the API. 164 | 165 | This function contains the logic that handles operation method parameters. 166 | 167 | Example: 168 | ```javascript 169 | const request = api.getRequestConfigForOperation('updatePet', [1, { name: 'Odie' }]) 170 | ``` 171 | 172 | #### Parameter: operation 173 | 174 | The operation to call. Either as an operation object or string (operationId). 175 | 176 | Type: `Operation` or `string` (operationId) 177 | 178 | #### Parameter: args 179 | 180 | The operation method arguments. 181 | 182 | Type: `OperationMethodArguments` 183 | 184 | ### .getAxiosInstance() 185 | 186 | Creates a new axios instance, extends it and returns it. 187 | 188 | While initalising with [`.init()`](#init) or [.initSync()](#initsync) OpenAPIClientAxios calls this function to create the member client. 189 | 190 | Note: Requires the API to be initalised first if run outside of .init() methods. 191 | 192 | ### .getAxiosConfigForOperation(operation, args) 193 | 194 | Creates an axios config for operation + arguments to be used for calling the API. 195 | 196 | This function calls `.getRequestConfigForOperation()` internally and maps the values to be suitable for axios. 197 | 198 | Returns an [AxiosRequestConfig](https://github.com/axios/axios#request-config) object 199 | 200 | Example: 201 | ```javascript 202 | const request = api.getAxiosConfigForOperation('getPets', [{ petId: 1 }]) 203 | ``` 204 | 205 | #### Parameter: operation 206 | 207 | The operation to call. Either as an operation object or string (operationId). 208 | 209 | Type: `Operation` or `string` (operationId) 210 | 211 | #### Parameter: args 212 | 213 | The operation method arguments. 214 | 215 | Type: `OperationMethodArguments` 216 | 217 | ## Axios Client Instance 218 | 219 | When OpenAPIClientAxios is initalised, a member 220 | [axios client instance](https://github.com/axios/axios#creating-an-instance) is created. 221 | 222 | The client instance can be accessed either directly via `api.client` getter, or `api.getClient()`. 223 | 224 | The member axios client instance is a regular instance of axios with [Operation Methods](#operation-method) created to 225 | provide an easy JavaScript API to call API operations. 226 | 227 | In addition to operation methods, the Axios client instance baseURL is pre-configured to match the first OpenAPI 228 | definition [servers](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#serverObject) property. 229 | 230 | The parent OpenAPIClientAxios instance can also be accessed from the client via `client.api`. 231 | 232 | ## Operation Method 233 | 234 | Operation methods are the main API used to call OpenAPI operations. 235 | 236 | Each method is generated during [`.init()`](#init) and is attached as a property to the axios client instance. 237 | 238 | ## Operation Method Arguments 239 | 240 | Each operation method takes three arguments: 241 | ```javascript 242 | client.operationId(parameters?, data?, config?) 243 | ``` 244 | 245 | ### Parameters 246 | 247 | The first argument is used to pass parameters available for the operation. 248 | 249 | ```javascript 250 | // GET /pets/{petId} 251 | client.getPet({ petId: 1 }) 252 | ``` 253 | 254 | For syntactic sugar purposes, you can also specify a single implicit parameter value, in which case OpenAPIClientAxios 255 | will look for the first required parameter for the operation. Usually this is will be a path parameter. 256 | 257 | ```javascript 258 | // GET /pets/{petId} - getPet 259 | client.getPet(1) 260 | ``` 261 | 262 | Alternatively, you can explicitly specify parameters in array form. This method allows you to set custom parameters not defined 263 | in the OpenAPI spec. 264 | 265 | ```javascript 266 | // GET /pets?search=Garfield - searchPets 267 | client.searchPets([{ name: 'search', value: 'Garfield', in: 'query' }]) 268 | ``` 269 | 270 | The type of the parameters can be any of: 271 | - query 272 | - header 273 | - path 274 | - cookie 275 | 276 | ### Data 277 | 278 | The second argument is used to pass the requestPayload 279 | 280 | ```javascript 281 | // PUT /pets/1 - updatePet 282 | client.updatePet(1, { name: 'Odie' }) 283 | ``` 284 | 285 | More complex payloads, such as Node.js streams or FormData supported by Axios can be used. 286 | 287 | The first argument can be set to null if there are no parameters required for the operation. 288 | 289 | ```javascript 290 | // POST /pets - createPet 291 | client.updatePet(null, { name: 'Garfield' }) 292 | ``` 293 | 294 | ### Config 295 | 296 | The last argument is the config object. 297 | 298 | The config object is an [`AxiosRequestConfig`](https://github.com/axios/axios#request-config) object. You can use it to 299 | override axios request config parameters, such as `headers`, `timeout`, `withCredentials` and many more. 300 | 301 | ```javascript 302 | // POST /user - createUser 303 | client.createUser(null, { user: 'admin', pass: '123' }, { headers: { 'x-api-key': 'secret' } }); 304 | ``` 305 | 306 | ## Request Config Object 307 | 308 | A `RequestConfig` object gets created as part of every operation method call. 309 | 310 | It represents a generic HTTP request to be executed. 311 | 312 | A request config object can be created without calling an operation method using 313 | [`.getRequestConfigForOperation()`](#getrequestconfigforoperationoperation-args) 314 | 315 | ```javascript 316 | import { RequestConfig } from 'openapi-client-axios'; 317 | ``` 318 | 319 | Example object 320 | ```javascript 321 | const requestConfig = { 322 | method: 'put', // HTTP method 323 | url: 'http://localhost:8000/pets/1?return=id,name', // full URL including protocol, host, path and query string 324 | path: '/pets/1', // path for the operation (relative to server base URL) 325 | pathParams: { petId: 1 }, // path parameters 326 | query: { return: ['id', 'name'] }, // query parameters 327 | queryString: 'return=id,name', // query string 328 | headers: { 329 | 'content-type': 'application/json;charset=UTF-8', 330 | 'accept': 'application/json' , 331 | 'cookie': 'x-api-key=secret', 332 | }, // HTTP headers, including cookie 333 | cookies: { 'x-api-key': 'secret' }, // cookies 334 | payload: { 335 | name: 'Garfield', 336 | age: 35, 337 | }, // the request payload passed as-is 338 | } 339 | ``` 340 | 341 | ## Paths Dictionary 342 | 343 | In addition to operationIds, OpenAPIClient also allows calling operation 344 | methods, using the operations' path and HTTP method. 345 | 346 | The paths dictionary contains each path found in the OAS definition as keys, 347 | and an object with each registered operation method as the value. 348 | 349 | Example: 350 | 351 | ```javascript 352 | client.paths['/pets'].get(); // GET /pets, same as calling client.getPets() 353 | client.paths['/pets'].post(); // POST /pets 354 | client.paths['/pets/{petId}'].put(1); // PUT /pets/1 355 | client.paths['/pets/{petId}/owner/{ownerId}'].get({ petId: 1, ownerId: 2 }) ; // GET /pets/1/owner/2 356 | ``` 357 | 358 | This allows calling operation methods without using their operationIds, which 359 | may be sometimes preferred. 360 | -------------------------------------------------------------------------------- /docs/openapi-client-axios/bundling.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 10 3 | --- 4 | 5 | # Bundling 6 | 7 | When using `openapi-client-axios` in a project with a bundler such as webpack, esbuild or rollup, you can include your 8 | openapi definition in the bundle to avoid a performance penalty from having to load the openapi file from a remote URL 9 | to initialize the client. 10 | 11 | This can be done by directly passing the definition as a loaded javascript object, instead of an URL: 12 | 13 | ```js 14 | import OpenAPIClientAxios from "openapi-client-axios"; 15 | import definition from "./openapi.json"; // most bundlers can load json files as importable javascript objects 16 | 17 | const api = new OpenAPIClientAxios({ definition }); 18 | ``` 19 | 20 | The bundling approach has the additional benefit of creating an atomic standalone client module, which doesn't depend 21 | on any external resources. 22 | 23 | ## Optimizing the bundle 24 | 25 | :::info 26 | 27 | In most cases you want to keep your javascript bundle as small as possible; so including the entire openapi definition 28 | file in the bundle may not be optimal. 29 | 30 | Consider all the metadata included in the OpenAPI spec such as examples, descriptions, schemas and contact 31 | information, none of which is essential for openapi-client-axios to operate during runtime. 32 | 33 | ::: 34 | 35 | [The `openapicmd read` command](/docs/openapicmd/intro/#openapi-read) ships with a `--strip` flag designed to remove 36 | all unnecessary metadata from your openapi file before bundling for use with openapi-client-axios. 37 | 38 | To create an optimized runtime version of your openapi definition: 39 | 40 | ```sh 41 | npx openapicmd read --strip openapi_client_axios --format json openapi.json > openapi-runtime.json 42 | ``` 43 | 44 | This file can then be included in your runtime bundle: 45 | 46 | ```js 47 | import definition from "./openapi-runtime.json"; 48 | 49 | const api = new OpenAPIClientAxios({ definition }); 50 | ``` 51 | 52 | :::caution 53 | 54 | Note that for [typegen](/docs/openapi-client-axios/typegen/), you should always use the full version of the openapi file, not the optimized runtime 55 | version created with openapicmd. 56 | 57 | ::: 58 | -------------------------------------------------------------------------------- /docs/openapi-client-axios/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Quick Start: Client" 3 | hide_title: true 4 | sidebar_position: 1 5 | --- 6 | 7 |
8 | openapi-client-axios logo 9 |

10 | openapi-client-axios 11 | GitHub 12 |

13 | 14 | [![CI](https://github.com/openapistack/openapi-client-axios/workflows/CI/badge.svg)](https://github.com/openapistack/openapi-client-axios/actions?query=workflow%3ACI) 15 | [![npm version](https://img.shields.io/npm/v/openapi-client-axios.svg)](https://www.npmjs.com/package/openapi-client-axios) 16 | [![npm downloads](https://img.shields.io/npm/dw/openapi-client-axios)](https://www.npmjs.com/package/openapi-client-axios) 17 | [![GitHub stars](https://img.shields.io/github/stars/openapistack/openapi-client-axios)](https://github.com/openapistack/openapi-client-axios) 18 | [![bundle size](https://img.shields.io/bundlephobia/minzip/openapi-client-axios?label=gzip%20bundle)](https://bundlephobia.com/package/openapi-client-axios) 19 | [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/openapistack/openapi-client-axios/blob/master/LICENSE) 20 | [![Buy me a coffee](https://img.shields.io/badge/donate-buy%20me%20a%20coffee-orange)](https://buymeacoff.ee/anttiviljami) 21 | 22 |

JavaScript client library for consuming OpenAPI-enabled APIs with axios. Types included.

23 |
24 | 25 | ## Features 26 | 27 | - [x] Create API clients from [OpenAPI v3 definitions](https://github.com/OAI/OpenAPI-Specification) 28 | - [x] Client is configured in runtime. **No generated code!** 29 | - [x] Generate TypeScript definitions (.d.ts) for your APIs with full IntelliSense support 30 | - [x] Easy to use API to call API operations using JavaScript methods 31 | - `client.getPet(1)` 32 | - `client.searchPets()` 33 | - `client.searchPets({ ids: [1, 2, 3] })` 34 | - `client.updatePet(1, payload)` 35 | - [x] Built on top of the robust [axios](https://github.com/axios/axios) JavaScript library 36 | - [x] Isomorphic, works both in browser and Node.js 37 | 38 | ## Quick Start 39 | 40 | ``` 41 | npm install --save axios openapi-client-axios 42 | ``` 43 | 44 | ``` 45 | yarn add axios openapi-client-axios 46 | ``` 47 | 48 | With promises / CommonJS syntax: 49 | 50 | ```javascript 51 | const OpenAPIClientAxios = require("openapi-client-axios").default; 52 | 53 | const api = new OpenAPIClientAxios({ 54 | definition: "https://example.com/api/openapi.json", 55 | }); 56 | api 57 | .init() 58 | .then((client) => client.getPetById(1)) 59 | .then((res) => console.log("Here is pet id:1 from the api", res.data)); 60 | ``` 61 | 62 | With async-await / ES6 syntax: 63 | 64 | ```javascript 65 | import OpenAPIClientAxios from "openapi-client-axios"; 66 | 67 | const api = new OpenAPIClientAxios({ 68 | definition: "https://example.com/api/openapi.json", 69 | }); 70 | api.init(); 71 | 72 | async function createPet() { 73 | const client = await api.getClient(); 74 | const res = await client.createPet(null, { name: "Garfield" }); 75 | console.log("Pet created", res.data); 76 | } 77 | ``` 78 | 79 | ## Typesafe Clients 80 | 81 | ![TypeScript IntelliSense](/img/intellisense.gif) 82 | 83 | `openapi-client-axios` comes with a tool called `typegen` to generate typescript type files (.d.ts) for 84 | OpenAPIClient instances using an OpenAPI definition file. 85 | 86 | ``` 87 | npx openapicmd typegen ./openapi.yaml > src/types/openapi.d.ts 88 | ``` 89 | 90 | The output file exports a type called `Client`, which can directly be used with instances created with `OpenAPIClientAxios`. 91 | 92 | ```typescript 93 | import type { Client as PetStoreClient } from "./openapi.d.ts"; 94 | 95 | const client = await api.getClient(); 96 | ``` 97 | -------------------------------------------------------------------------------- /docs/openapi-client-axios/typegen.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: Typegen 4 | --- 5 | 6 | # Generating Types 7 | 8 | :::tip 9 | 10 | It's recommended to use [`openapicmd typegen`](/docs/openapicmd/typegen/) to generate types instead of directly installing the typegen package. 11 | 12 | ::: 13 | 14 | `openapi-client-axios-typegen` is a command line tool to generate easy to use Typescript types from 15 | OpenAPI files. 16 | 17 | ![TypeScript IntelliSense](/img/intellisense.gif) 18 | 19 | ## Usage 20 | 21 | ``` 22 | npm install -g openapi-client-axios-typegen 23 | ``` 24 | 25 | or with npx: 26 | 27 | ``` 28 | npx openapi-client-axios-typegen 29 | ``` 30 | 31 | ``` 32 | Usage: typegen [file] 33 | 34 | Options: 35 | --help Show help [boolean] 36 | --version Show version number [boolean] 37 | -t, --transformOperationName [string] 38 | 39 | Examples: 40 | typegen ./openapi.yml > openapi.d.ts 41 | typegen https://openapistack.co/petstore.openapi.json > openapi.d.ts 42 | ``` 43 | 44 | `typegen` supports both local and remote files: 45 | 46 | ``` 47 | typegen ./openapi.yaml > openapi.d.ts # local file 48 | typegen https://petstore3.swagger.io/api/v3/openapi.json > openapi.d.ts # remote url 49 | ``` 50 | 51 | ## Typesafe Clients 52 | 53 | The output of `typegen` exports a type called `Client`, which can be directly used with clients created with `OpenAPIClientAxios`. 54 | 55 | Both the `api.getClient()` and `api.init()` methods support passing in a Client type. 56 | 57 | ```typescript 58 | import { Client as PetStoreClient } from "./openapi.d.ts"; 59 | 60 | const client = await api.init(); 61 | const client = await api.getClient(); 62 | ``` 63 | 64 | ## Importing generated types 65 | 66 | You can directly import types defined as schemas in your openapi spec as Typescript types: 67 | 68 | ```ts 69 | import type { Pet, User } from "./openapi.d.ts"; 70 | 71 | const myPet: Pet = { 72 | id: 1, 73 | name: "My Pet", 74 | tag: "My Tag", 75 | }; 76 | 77 | const myUser: User = { 78 | id: 1, 79 | name: "My User", 80 | }; 81 | ``` 82 | 83 | ## Advanced: `--transformOperationName` 84 | 85 | You can provide a predicate function to typegen to transform operation names in the output type file. 86 | 87 | The function needs to be exported from a module: 88 | 89 | ```ts 90 | // operation-transform.ts 91 | export const prefix = (operationId: string) => ["$", operationId].join(""); 92 | ``` 93 | 94 | Example usage: 95 | 96 | ``` 97 | typegen ./openapi.yaml --transformOperationName operation-transform.prefix > openapi.d.ts 98 | ``` 99 | 100 | ## Example type file 101 | 102 | Here's a full example of a generated type file: 103 | 104 | ```ts 105 | // openapi.d.ts 106 | 107 | import type { 108 | OpenAPIClient, 109 | Parameters, 110 | UnknownParamsObject, 111 | OperationResponse, 112 | AxiosRequestConfig, 113 | } from "openapi-client-axios"; 114 | 115 | declare namespace Components { 116 | namespace RequestBodies { 117 | export type PetPayload = Schemas.PetPayload; 118 | } 119 | namespace Schemas { 120 | /** 121 | * PetId 122 | * Unique identifier for pet in database 123 | * example: 124 | * 1 125 | */ 126 | export type PetId = number; 127 | export interface PetPayload { 128 | /** 129 | * PetName 130 | * Name of the pet 131 | * example: 132 | * Garfield 133 | */ 134 | name: string; 135 | } 136 | /** 137 | * QueryLimit 138 | * Number of items to return 139 | * example: 140 | * 25 141 | */ 142 | export type QueryLimit = number; 143 | /** 144 | * QueryOffset 145 | * Starting offset for returning items 146 | * example: 147 | * 0 148 | */ 149 | export type QueryOffset = number; 150 | } 151 | } 152 | declare namespace Paths { 153 | namespace CreatePet { 154 | export type RequestBody = Components.RequestBodies.PetPayload; 155 | namespace Responses { 156 | export interface $201 {} 157 | } 158 | } 159 | namespace DeletePetById { 160 | namespace Parameters { 161 | export type Id = 162 | /** 163 | * PetId 164 | * Unique identifier for pet in database 165 | * example: 166 | * 1 167 | */ 168 | Components.Schemas.PetId; 169 | } 170 | export interface PathParameters { 171 | id: Parameters.Id; 172 | } 173 | namespace Responses { 174 | export interface $200 {} 175 | export interface $404 {} 176 | } 177 | } 178 | namespace GetOwnerByPetId { 179 | namespace Parameters { 180 | export type Id = 181 | /** 182 | * PetId 183 | * Unique identifier for pet in database 184 | * example: 185 | * 1 186 | */ 187 | Components.Schemas.PetId; 188 | } 189 | export interface PathParameters { 190 | id: Parameters.Id; 191 | } 192 | namespace Responses { 193 | export interface $200 {} 194 | export interface $404 {} 195 | } 196 | } 197 | namespace GetPetById { 198 | namespace Parameters { 199 | export type Id = 200 | /** 201 | * PetId 202 | * Unique identifier for pet in database 203 | * example: 204 | * 1 205 | */ 206 | Components.Schemas.PetId; 207 | } 208 | export interface PathParameters { 209 | id: Parameters.Id; 210 | } 211 | namespace Responses { 212 | export interface $200 {} 213 | export interface $404 {} 214 | } 215 | } 216 | namespace GetPetOwner { 217 | namespace Parameters { 218 | export type OwnerId = 219 | /** 220 | * PetId 221 | * Unique identifier for pet in database 222 | * example: 223 | * 1 224 | */ 225 | Components.Schemas.PetId; 226 | export type PetId = 227 | /** 228 | * PetId 229 | * Unique identifier for pet in database 230 | * example: 231 | * 1 232 | */ 233 | Components.Schemas.PetId; 234 | } 235 | export interface PathParameters { 236 | petId: Parameters.PetId; 237 | ownerId: Parameters.OwnerId; 238 | } 239 | namespace Responses { 240 | export interface $200 {} 241 | export interface $404 {} 242 | } 243 | } 244 | namespace GetPets { 245 | namespace Parameters { 246 | export type Limit = 247 | /** 248 | * QueryLimit 249 | * Number of items to return 250 | * example: 251 | * 25 252 | */ 253 | Components.Schemas.QueryLimit; 254 | export type Offset = 255 | /** 256 | * QueryOffset 257 | * Starting offset for returning items 258 | * example: 259 | * 0 260 | */ 261 | Components.Schemas.QueryOffset; 262 | } 263 | export interface QueryParameters { 264 | limit?: Parameters.Limit; 265 | offset?: Parameters.Offset; 266 | } 267 | namespace Responses { 268 | export interface $200 {} 269 | } 270 | } 271 | namespace GetPetsMeta { 272 | namespace Responses { 273 | export interface $200 {} 274 | } 275 | } 276 | namespace GetPetsRelative { 277 | namespace Responses { 278 | export interface $200 {} 279 | } 280 | } 281 | namespace ReplacePetById { 282 | namespace Parameters { 283 | export type Id = 284 | /** 285 | * PetId 286 | * Unique identifier for pet in database 287 | * example: 288 | * 1 289 | */ 290 | Components.Schemas.PetId; 291 | } 292 | export interface PathParameters { 293 | id: Parameters.Id; 294 | } 295 | export type RequestBody = Components.RequestBodies.PetPayload; 296 | namespace Responses { 297 | export interface $200 {} 298 | export interface $404 {} 299 | } 300 | } 301 | namespace UpdatePetById { 302 | namespace Parameters { 303 | export type Id = 304 | /** 305 | * PetId 306 | * Unique identifier for pet in database 307 | * example: 308 | * 1 309 | */ 310 | Components.Schemas.PetId; 311 | } 312 | export interface PathParameters { 313 | id: Parameters.Id; 314 | } 315 | export type RequestBody = Components.RequestBodies.PetPayload; 316 | namespace Responses { 317 | export interface $200 {} 318 | export interface $404 {} 319 | } 320 | } 321 | } 322 | 323 | export interface OperationMethods { 324 | /** 325 | * getPets - List pets 326 | * 327 | * Returns all pets in database 328 | */ 329 | "getPets"( 330 | parameters?: Parameters | null, 331 | data?: any, 332 | config?: AxiosRequestConfig 333 | ): OperationResponse; 334 | /** 335 | * createPet - Create a pet 336 | * 337 | * Crete a new pet into the database 338 | */ 339 | "createPet"( 340 | parameters?: Parameters | null, 341 | data?: Paths.CreatePet.RequestBody, 342 | config?: AxiosRequestConfig 343 | ): OperationResponse; 344 | /** 345 | * getPetById - Get a pet 346 | * 347 | * Returns a pet by its id in database 348 | */ 349 | "getPetById"( 350 | parameters?: Parameters | null, 351 | data?: any, 352 | config?: AxiosRequestConfig 353 | ): OperationResponse; 354 | /** 355 | * replacePetById - Replace pet 356 | * 357 | * Replace an existing pet in the database 358 | */ 359 | "replacePetById"( 360 | parameters?: Parameters | null, 361 | data?: Paths.ReplacePetById.RequestBody, 362 | config?: AxiosRequestConfig 363 | ): OperationResponse; 364 | /** 365 | * updatePetById - Update pet 366 | * 367 | * Update an existing pet in the database 368 | */ 369 | "updatePetById"( 370 | parameters?: Parameters | null, 371 | data?: Paths.UpdatePetById.RequestBody, 372 | config?: AxiosRequestConfig 373 | ): OperationResponse; 374 | /** 375 | * deletePetById - Delete a pet 376 | * 377 | * Deletes a pet by its id in database 378 | */ 379 | "deletePetById"( 380 | parameters?: Parameters | null, 381 | data?: any, 382 | config?: AxiosRequestConfig 383 | ): OperationResponse; 384 | /** 385 | * getOwnerByPetId - Get a pet's owner 386 | * 387 | * Get the owner for a pet 388 | */ 389 | "getOwnerByPetId"( 390 | parameters?: Parameters | null, 391 | data?: any, 392 | config?: AxiosRequestConfig 393 | ): OperationResponse; 394 | /** 395 | * getPetOwner - Get owner by id 396 | * 397 | * Get the owner for a pet 398 | */ 399 | "getPetOwner"( 400 | parameters?: Parameters | null, 401 | data?: any, 402 | config?: AxiosRequestConfig 403 | ): OperationResponse; 404 | /** 405 | * getPetsMeta - Get pet metadata 406 | * 407 | * Returns a list of metadata about pets and their relations in the database 408 | */ 409 | "getPetsMeta"( 410 | parameters?: Parameters | null, 411 | data?: any, 412 | config?: AxiosRequestConfig 413 | ): OperationResponse; 414 | /** 415 | * getPetsRelative - Get pet metadata 416 | * 417 | * Returns a list of metadata about pets and their relations in the database 418 | */ 419 | "getPetsRelative"( 420 | parameters?: Parameters | null, 421 | data?: any, 422 | config?: AxiosRequestConfig 423 | ): OperationResponse; 424 | } 425 | 426 | export interface PathsDictionary { 427 | ["/pets"]: { 428 | /** 429 | * getPets - List pets 430 | * 431 | * Returns all pets in database 432 | */ 433 | "get"( 434 | parameters?: Parameters | null, 435 | data?: any, 436 | config?: AxiosRequestConfig 437 | ): OperationResponse; 438 | /** 439 | * createPet - Create a pet 440 | * 441 | * Crete a new pet into the database 442 | */ 443 | "post"( 444 | parameters?: Parameters | null, 445 | data?: Paths.CreatePet.RequestBody, 446 | config?: AxiosRequestConfig 447 | ): OperationResponse; 448 | }; 449 | ["/pets/{id}"]: { 450 | /** 451 | * getPetById - Get a pet 452 | * 453 | * Returns a pet by its id in database 454 | */ 455 | "get"( 456 | parameters?: Parameters | null, 457 | data?: any, 458 | config?: AxiosRequestConfig 459 | ): OperationResponse; 460 | /** 461 | * replacePetById - Replace pet 462 | * 463 | * Replace an existing pet in the database 464 | */ 465 | "put"( 466 | parameters?: Parameters | null, 467 | data?: Paths.ReplacePetById.RequestBody, 468 | config?: AxiosRequestConfig 469 | ): OperationResponse; 470 | /** 471 | * updatePetById - Update pet 472 | * 473 | * Update an existing pet in the database 474 | */ 475 | "patch"( 476 | parameters?: Parameters | null, 477 | data?: Paths.UpdatePetById.RequestBody, 478 | config?: AxiosRequestConfig 479 | ): OperationResponse; 480 | /** 481 | * deletePetById - Delete a pet 482 | * 483 | * Deletes a pet by its id in database 484 | */ 485 | "delete"( 486 | parameters?: Parameters | null, 487 | data?: any, 488 | config?: AxiosRequestConfig 489 | ): OperationResponse; 490 | }; 491 | ["/pets/{id}/owner"]: { 492 | /** 493 | * getOwnerByPetId - Get a pet's owner 494 | * 495 | * Get the owner for a pet 496 | */ 497 | "get"( 498 | parameters?: Parameters | null, 499 | data?: any, 500 | config?: AxiosRequestConfig 501 | ): OperationResponse; 502 | }; 503 | ["/pets/{petId}/owner/{ownerId}"]: { 504 | /** 505 | * getPetOwner - Get owner by id 506 | * 507 | * Get the owner for a pet 508 | */ 509 | "get"( 510 | parameters?: Parameters | null, 511 | data?: any, 512 | config?: AxiosRequestConfig 513 | ): OperationResponse; 514 | }; 515 | ["/pets/meta"]: { 516 | /** 517 | * getPetsMeta - Get pet metadata 518 | * 519 | * Returns a list of metadata about pets and their relations in the database 520 | */ 521 | "get"( 522 | parameters?: Parameters | null, 523 | data?: any, 524 | config?: AxiosRequestConfig 525 | ): OperationResponse; 526 | }; 527 | ["/pets/relative"]: { 528 | /** 529 | * getPetsRelative - Get pet metadata 530 | * 531 | * Returns a list of metadata about pets and their relations in the database 532 | */ 533 | "get"( 534 | parameters?: Parameters | null, 535 | data?: any, 536 | config?: AxiosRequestConfig 537 | ): OperationResponse; 538 | }; 539 | } 540 | 541 | export type Client = OpenAPIClient; 542 | 543 | export type PetId = Components.Schemas.PetId; 544 | export type PetPayload = Components.Schemas.PetPayload; 545 | export type QueryLimit = Components.Schemas.QueryLimit; 546 | export type QueryOffset = Components.Schemas.QueryOffset; 547 | ``` 548 | -------------------------------------------------------------------------------- /docs/openapi-client-axios/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Operation Methods 6 | 7 | OpenAPI Client Axios uses [operationIds](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operation-object) 8 | in OpenAPIv3 definitions to call API operations. 9 | 10 | After initializing `OpenAPIClientAxios`, an axios client instance extended with OpenAPI capabilities is exposed. 11 | 12 | Example: 13 | 14 | ```javascript 15 | const api = new OpenAPIClientAxios({ 16 | definition: "https://example.com/api/openapi.json", 17 | }); 18 | api.init().then((client) => { 19 | client.updatePet(1, { age: 12 }); 20 | }); 21 | ``` 22 | 23 | `client` is an [axios instance](https://github.com/axios/axios#creating-an-instance) initialized with 24 | baseURL from OpenAPI definitions and extended with extra operation methods for calling API operations. 25 | 26 | It also has a reference to OpenAPIClientAxios at `client.api` 27 | 28 | ## Operation methods 29 | 30 | OpenAPIClientAxios operation methods take 3 arguments: 31 | 32 | ```javascript 33 | client.operationId(parameters?, data?, config?) 34 | ``` 35 | 36 | ### Parameters 37 | 38 | The first argument is used to pass parameters available for the operation. 39 | 40 | ```javascript 41 | // GET /pets/{petId} 42 | client.getPet({ petId: 1 }); 43 | ``` 44 | 45 | For syntactic sugar purposes, you can also specify a single implicit parameter value, in which case OpenAPIClientAxios 46 | will look for the first required parameter for the operation. Usually this is will be a path parameter. 47 | 48 | ```javascript 49 | // GET /pets/{petId} - getPet 50 | client.getPet(1); 51 | ``` 52 | 53 | Alternatively, you can explicitly specify parameters in array form. This method allows you to set custom parameters not defined 54 | in the OpenAPI spec. 55 | 56 | ```javascript 57 | // GET /pets?search=Garfield - searchPets 58 | client.searchPets([{ name: "search", value: "Garfield", in: "query" }]); 59 | ``` 60 | 61 | The type of the parameters can be any of: 62 | 63 | - query 64 | - header 65 | - path 66 | - cookie 67 | 68 | ### Data 69 | 70 | The second argument is used to pass the requestPayload 71 | 72 | ```javascript 73 | // PUT /pets/1 - updatePet 74 | client.updatePet(1, { name: "Odie" }); 75 | ``` 76 | 77 | More complex payloads, such as Node.js streams or FormData supported by Axios can be used. 78 | 79 | The first argument can be set to null if there are no parameters required for the operation. 80 | 81 | ```javascript 82 | // POST /pets - createPet 83 | client.updatePet(null, { name: "Garfield" }); 84 | ``` 85 | 86 | ### Config object 87 | 88 | The last argument is the config object. 89 | 90 | The config object is an [`AxiosRequestConfig`](https://github.com/axios/axios#request-config) object. You can use it to 91 | override axios request config parameters, such as `headers`, `timeout`, `withCredentials` and many more. 92 | 93 | ```javascript 94 | // POST /user - createUser 95 | client.createUser( 96 | null, 97 | { user: "admin", pass: "123" }, 98 | { headers: { "x-api-key": "secret" } } 99 | ); 100 | ``` 101 | 102 | ## Paths Dictionary 103 | 104 | OpenAPI Client Axios also allows calling API operations via their path and HTTP 105 | method, using the paths dictionary. 106 | 107 | Example: 108 | 109 | ```javascript 110 | client.paths["/pets"].get(); // GET /pets, same as calling client.getPets() 111 | client.paths["/pets"].post(); // POST /pets 112 | client.paths["/pets/{petId}"].put(1); // PUT /pets/1 113 | client.paths["/pets/{petId}/owner/{ownerId}"].get({ petId: 1, ownerId: 2 }); // GET /pets/1/owner/2 114 | ``` 115 | 116 | This allows calling operation methods without using their operationIds, which 117 | may be sometimes preferred. 118 | -------------------------------------------------------------------------------- /docs/openapicmd/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "CLI", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /docs/openapicmd/call.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Call API operations 6 | 7 | :::info 8 | 9 | The call command can be used to call APIs from the command line for testing or automation purposes e.g. in CI pipelines or shell scripts. 10 | 11 | ::: 12 | 13 | Use the `call` command to call operations in your API from the command line: 14 | 15 | ``` 16 | openapi call https://petstore3.swagger.io/api/v3/openapi.json 17 | ``` 18 | 19 | or with npx: 20 | 21 | ``` 22 | npx openapicmd call https://petstore3.swagger.io/api/v3/openapi.json 23 | ``` 24 | 25 | ## Interactive Mode 26 | 27 | :::tip 28 | 29 | In non-TTY environments openapicmd will not run in interactive mode. See how to pass [parameters](#parameters) instead. 30 | 31 | ::: 32 | 33 | By default, openapicmd will act in interactive mode to guide with calling the API: 34 | 35 | ``` 36 | ? select operation 37 | ❯ GET /pet/{petId} - Find pet by ID (getPetById) 38 | POST /pet/{petId} - Updates a pet in the store with form data (updatePetWithForm) 39 | DELETE /pet/{petId} - Deletes a pet (deletePet) 40 | POST /pet/{petId}/uploadImage - uploads an image (uploadFile) 41 | GET /store/inventory - Returns pet inventories by status (getInventory) 42 | POST /store/order - Place an order for a pet (placeOrder) 43 | GET /store/order/{orderId} - Find purchase order by ID (getOrderById) 44 | (Move up and down to reveal more choices) 45 | ``` 46 | 47 | ``` 48 | ? select operation GET /pet/{petId} - Find pet by ID (getPetById) 49 | petId: 1 50 | ``` 51 | 52 | ``` 53 | ? use security scheme 54 | ◯ api_key 55 | ◯ petstore_auth 56 | 57 | ``` 58 | 59 | Result: 60 | 61 | ```json 62 | GET /pet/1 63 | { 64 | "id": 1, 65 | "name": "Dogs", 66 | "photoUrls": [ 67 | "nulla mollit veniam ea" 68 | ], 69 | "tags": [ 70 | { 71 | "id": -21297897, 72 | "name": "incididunt sed eiusmod" 73 | } 74 | ], 75 | "status": "pending" 76 | } 77 | ``` 78 | 79 | ## Parameters 80 | 81 | You can pass parameters directly as command line flags to skip interactive mode: 82 | 83 | ``` 84 | openapi call https://petstore3.swagger.io/api/v3/openapi.json -o updatePet -d '{"id": 1, "name":"Cats"}' -p petId=1 --security=none 85 | ``` 86 | 87 | - operationId can be speficified with the `-o` or `--operation` 88 | - Path, query and header parameters can be passed with the `-p` or `--param` flag. 89 | - Request bodies can be passed with the `-d` or `--data` flag. 90 | - Raw headers can be passed with the `-H` or `--header` flag. 91 | 92 | To gain more information about the request and response, you can use the `-i` or `--include` flag. 93 | 94 | ## Authorization 95 | 96 | The call command supports most security schemes defined in OpenAPI v3. 97 | 98 | ### Setting Headers 99 | 100 | You can directly set headers such as api keys or `authorization` using the `-H` or `--header` flag. 101 | 102 | ``` 103 | openapi call https://petstore3.swagger.io/api/v3/openapi.json -o getInventory -H 'authorization: Bearer token123' 104 | ``` 105 | 106 | ### Basic Auth 107 | 108 | For basic auth, you can directly pass `--username` and `--passsword` 109 | 110 | ``` 111 | openapi call https://petstore3.swagger.io/api/v3/openapi.json -o getInventory --username admin --password secret123 112 | ``` 113 | 114 | ### Bearer Token 115 | 116 | If your API defines a Bearer Token security scheme e.g. OAuth2, you can pass a token using the `--token` flag. 117 | 118 | ``` 119 | openapi call https://petstore3.swagger.io/api/v3/openapi.json -o getInventory --token=eyJhbGciOiJIUzI1.... 120 | ``` 121 | 122 | ### API Key 123 | 124 | If your API defines an API Key security scheme, you can directly pass the key / token using the `--api-key` flag. 125 | 126 | ``` 127 | openapi call https://petstore3.swagger.io/api/v3/openapi.json -o getInventory --api-key=secret123 128 | ``` 129 | -------------------------------------------------------------------------------- /docs/openapicmd/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 10 3 | title: .openapiconfig 4 | hide_title: true 5 | --- 6 | 7 | # .openapiconfig 8 | 9 | :::tip 10 | 11 | You can use the `load` command to create a `.openapiconfig` file to avoid having to pass the openapi document when running openapicmd commands. 12 | 13 | ::: 14 | 15 | openapicmd tries to load a `.openapiconfig` file in the current working directory or parent directories for default parameters. 16 | 17 | Supported config filenames are `.openapiconfig` `.openapiconfig.yml` `openapiconfig.yaml` 18 | 19 | ## Loading a definition file 20 | 21 | To avoid having to pass the openapi file as an argument to openapicmd commands, you can _load_ a document, which creates or updates a `.openapiconfig` file. 22 | 23 | ``` 24 | openapi load https://petstore3.swagger.io/api/v3/openapi.json 25 | ``` 26 | 27 | Now you can run commands without passing the definition: 28 | 29 | ``` 30 | $ openapi info 31 | Loaded: https://petstore3.swagger.io/api/v3/openapi.json 32 | 33 | title: Swagger Petstore - OpenAPI 3.0 34 | version: 1.0.17 35 | description: 36 | This is a sample Pet Store Server based on the OpenAPI 3.0 specification. 37 | ``` 38 | 39 | ## Authentication 40 | 41 | You can also set up authentication strategies for [API calls](/docs/openapicmd/call#authorization) using the `auth` command. 42 | 43 | ``` 44 | openapi auth https://petstore3.swagger.io/api/v3/openapi.json 45 | ? use security scheme api_key 46 | ? api_key: Set API key (api_key) secret123 47 | Wrote auth config to /Users/viljamikuosmanen/Projects/openapi-stack/.openapiconfig. You can now use openapi call with the following auth configs: 48 | - api_key 49 | ``` 50 | 51 | ## Unloading 52 | 53 | The `unload` command can be used to quickly delete an existing `.openapiconfig` file. 54 | 55 | ``` 56 | openapi unload 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/openapicmd/generating-documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | title: Generating documentation 4 | --- 5 | 6 | # Generating Documentation 7 | 8 | ## Swagger UI 9 | 10 | :::info 11 | 12 | [Swagger UI](https://swagger.io/tools/swagger-ui/) is an open source browser interface used to visualize OpenAPI documents. 13 | 14 | ::: 15 | 16 | You can use the `swagger-ui` command quickly launch Swagger UI in your browser to preview your API. 17 | 18 | ``` 19 | openapi swagger-ui ./openapi.yml 20 | ``` 21 | 22 | ``` 23 | Swagger UI running at http://localhost:9000 24 | OpenAPI definition at http://localhost:9000/openapi.json 25 | ``` 26 | 27 | [![Screenshot of Swagger UI](/img/swagger-ui-screenshot.png)](/img/swagger-ui-screenshot.png) 28 | 29 | ## ReDoc 30 | 31 | :::info 32 | 33 | [ReDoc](https://github.com/Redocly/redoc) is an alternative open source browser interface used to visualize OpenAPI documents in a three-panel, responsive layout:. 34 | 35 | ::: 36 | 37 | You can use the `redoc` command quickly launch ReDoc in your browser to preview your API. 38 | 39 | ``` 40 | openapi redoc ./openapi.yml 41 | ``` 42 | 43 | ``` 44 | ReDoc running at http://localhost:9000 45 | OpenAPI definition at http://localhost:9000/openapi.json 46 | ``` 47 | 48 | [![Screenshot of ReDoc](/img/redoc-screenshot.png)](/img/redoc-screenshot.png) 49 | 50 | ## Generating Static Documentation 51 | 52 | :::tip 53 | 54 | You can create a standalone static website that can be deployed to any static page hosting provider (e.g. GitHub pages or Vercel) using the `--bundle` option. 55 | 56 | ::: 57 | 58 | To bundle your API documentation into a static website that can be deployed, use the `--bundle` option. 59 | 60 | ``` 61 | openapi swagger-ui ./openapi.yml --bundle=outDir 62 | openapi redoc ./openapi.yml --bundle=outDir 63 | ``` 64 | 65 | ## Proxy (Avoiding CORS) 66 | 67 | Often remote APIs do not support calling from localhost due to CORS configuration. To work around that, you can use the `--proxy` option to route API requests from Swagger UI via a local proxy to the remote API. 68 | 69 | ``` 70 | openapi swagger-ui ./openapi.yml --proxy 71 | ``` 72 | 73 | ``` 74 | Swagger UI running at http://localhost:9000 75 | OpenAPI definition at http://localhost:9000/openapi.json 76 | Proxy running at http://localhost:9001/proxy 77 | ``` 78 | 79 | ## Swagger Editor 80 | 81 | To design and edit OpenAPI files using Swagger Editor, you can use the `swagger-editor` command: 82 | 83 | ``` 84 | openapi swagger-ui ./openapi.yml 85 | ``` 86 | 87 | ``` 88 | Swagger Editor running at http://localhost:9000 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/openapicmd/mock-server.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Mock Server 6 | 7 | To quickly spin up a local mock server for an openapi, use the `mock` command. 8 | 9 | ``` 10 | openapi mock ./openapi.yml 11 | 12 | Mock server running at http://localhost:9000 13 | OpenAPI definition at http://localhost:9000/openapi.json 14 | ``` 15 | 16 | :::tip 17 | 18 | You can then use the `call` or `swagger-ui` commands to test your mock API. 19 | 20 | ``` 21 | npx openapi call http://localhost:9000/openapi.json 22 | ``` 23 | 24 | ::: 25 | -------------------------------------------------------------------------------- /docs/openapicmd/typegen.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Generating types 6 | 7 | Use the `typegen` command to generate Typescript types from your OpenAPI definition. 8 | 9 | ``` 10 | openapi typegen ./openapi.yml > openapi.d.ts 11 | ``` 12 | 13 | or with npx: 14 | 15 | ``` 16 | npx openapicmd typegen ./openapi.yml > openapi.d.ts 17 | ``` 18 | 19 | :::tip 20 | 21 | You can also use remote URLs to pass your openapi spec: 22 | 23 | ``` 24 | openapi typegen https://example.openapistack.co/openapi.json 25 | ``` 26 | 27 | ::: 28 | 29 | ## Usage 30 | 31 | ``` 32 | Generate types from openapi definition 33 | 34 | USAGE 35 | $ openapi typegen [DEFINITION] [-h] [-D] [-B] [-R /] [-H ...] [-V] [-S http://localhost:9000...] [-I 36 | {"info":{"version":"1.0.0"}}...] [-E x-internal] [-C default|all|openapi_client_axios|openapi_backend] [-U] [-b 37 | ] [--client] [--backend] [-A] 38 | 39 | ARGUMENTS 40 | DEFINITION input definition file 41 | 42 | FLAGS 43 | -A, --[no-]type-aliases Generate module level type aliases for schema components 44 | defined in spec 45 | -B, --bundle resolve remote $ref pointers 46 | -C, --strip=default|all|openapi_client_axios|openapi_backend Strip optional metadata such as examples and 47 | descriptions from definition 48 | -D, --dereference resolve $ref pointers 49 | -E, --exclude-ext=x-internal Specify an openapi extension to exclude parts of the 50 | spec 51 | -H, --header=... add request headers when calling remote urls 52 | -I, --inject={"info":{"version":"1.0.0"}}... inject JSON to definition with deep merge 53 | -R, --root=/ override API root path 54 | -S, --server=http://localhost:9000... override servers definition 55 | -U, --[no-]remove-unreferenced Remove unreferenced components, you can skip individual 56 | component being removed by setting x-openapicmd-keep to 57 | true 58 | -V, --validate validate against openapi schema 59 | -b, --banner= include a banner comment at the top of the generated 60 | file 61 | -h, --help Show CLI help. 62 | --backend Generate types for openapi-backend 63 | --client Generate types for openapi-client-axios (default) 64 | 65 | EXAMPLES 66 | $ openapi typegen --client ./openapi.yml > openapi.d.ts 67 | $ openapi typegen --backend ./openapi.yml > openapi.d.ts 68 | ``` 69 | 70 | ## Importing generated types 71 | 72 | You can directly import types defined as schemas in your openapi spec as Typescript types: 73 | 74 | ```ts 75 | import type { Pet, User } from "./openapi.d.ts"; 76 | 77 | const myPet: Pet = { 78 | id: 1, 79 | name: "My Pet", 80 | tag: "My Tag", 81 | }; 82 | 83 | const myUser: User = { 84 | id: 1, 85 | name: "My User", 86 | }; 87 | ``` 88 | 89 | ## Typesafe Clients 90 | 91 | The output of `openapi typegen --client` exports a type called `Client`, which can be directly used with clients created with `OpenAPIClientAxios`. 92 | 93 | Both the `api.getClient()` and `api.init()` methods support passing in a Client type. 94 | 95 | ```typescript 96 | import { Client as PetStoreClient } from "./openapi.d.ts"; 97 | 98 | const client = await api.init(); 99 | const client = await api.getClient(); 100 | ``` 101 | 102 | ![TypeScript IntelliSense](/img/intellisense.gif) 103 | 104 | ## Typesafe Backends 105 | 106 | To generate types for the backend, use `openapi typegen --backend`. 107 | 108 | See documentation for [usage of openapi-backend with TypeScript](/docs/openapi-backend/typescript). 109 | -------------------------------------------------------------------------------- /docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const lightCodeTheme = require('prism-react-renderer/themes/github'); 3 | const darkCodeTheme = require('prism-react-renderer/themes/dracula'); 4 | 5 | const Footer = ` 6 |
OpenAPI Stack is Free and Open Source Software (FOSS) licensed under the MIT license.
7 | 8 | ` 28 | 29 | /** @type {import('@docusaurus/types').Config} */ 30 | const config = { 31 | title: 'openapi-stack', 32 | tagline: 'Full stack typesafe API-first development with REST.', 33 | favicon: 'img/favicon.ico', 34 | 35 | url: 'https://openapistack.co', 36 | baseUrl: '/', 37 | 38 | organizationName: 'openapistack', 39 | projectName: 'docs', 40 | deploymentBranch: 'gh-pages', 41 | trailingSlash: true, 42 | 43 | onBrokenLinks: 'throw', 44 | onBrokenMarkdownLinks: 'warn', 45 | 46 | i18n: { 47 | defaultLocale: 'en', 48 | locales: ['en'], 49 | }, 50 | presets: [ 51 | [ 52 | 'classic', 53 | /** @type {import('@docusaurus/preset-classic').Options} */ 54 | ({ 55 | docs: { 56 | sidebarPath: require.resolve('./sidebars.js'), 57 | editUrl: 'https://github.com/openapistack/docs/edit/main/', 58 | }, 59 | theme: { 60 | customCss: require.resolve('./src/css/custom.css'), 61 | }, 62 | gtag: { 63 | trackingID: 'G-Y6PMFWM5ZP', 64 | anonymizeIP: true, 65 | } 66 | }), 67 | ], 68 | ], 69 | themeConfig: 70 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 71 | ({ 72 | colorMode: { 73 | disableSwitch: true, 74 | defaultMode: 'light', 75 | }, 76 | image: 'img/openapistack-social.jpg?version=2', 77 | navbar: { 78 | title: 'openapi-stack', 79 | logo: { 80 | alt: 'openapi-stack Logo', 81 | src: 'img/openapi-stack-logo.png', 82 | }, 83 | items: [ 84 | { 85 | to: '/docs/intro', 86 | position: 'left', 87 | label: 'Documentation', 88 | }, 89 | { 90 | to: '/docs/openapi-backend/intro', 91 | label: 'Backend', 92 | position: 'left', 93 | }, 94 | { 95 | to: '/docs/openapi-client-axios/intro', 96 | label: 'Client', 97 | position: 'left', 98 | }, 99 | { 100 | to: '/docs/openapicmd/intro', 101 | label: 'CLI', 102 | position: 'left', 103 | }, 104 | { 105 | href: 'https://github.com/openapistack', 106 | label: 'GitHub', 107 | position: 'right', 108 | }, 109 | { 110 | to: '/docs/examples', 111 | position: 'left', 112 | label: 'Examples', 113 | }, 114 | ], 115 | }, 116 | footer: { 117 | style: 'dark', 118 | links: [ 119 | { 120 | title: 'Content', 121 | items: [ 122 | { 123 | label: 'Documentation', 124 | to: '/docs/intro', 125 | }, 126 | { 127 | label: 'Why API First?', 128 | href: '/docs/api-first', 129 | }, 130 | { 131 | label: 'Example Projects', 132 | to: '/docs/examples/boilerplate/', 133 | }, 134 | { 135 | label: 'Backend Reference', 136 | href: '/docs/openapi-backend/api' 137 | }, 138 | { 139 | label: 'Client Reference', 140 | href: '/docs/openapi-client-axios/api' 141 | }, 142 | ], 143 | }, 144 | { 145 | title: 'Libraries', 146 | items: [ 147 | { 148 | label: 'openapi-backend', 149 | href: '/docs/openapi-backend/intro', 150 | }, 151 | { 152 | label: 'openapi-client-axios', 153 | href: '/docs/openapi-client-axios/intro', 154 | }, 155 | { 156 | label: 'openapicmd', 157 | href: '/docs/openapicmd/intro', 158 | }, 159 | ], 160 | }, 161 | { 162 | title: 'Links', 163 | items: [ 164 | { 165 | label: 'License & Imprint', 166 | href: 'https://openapistack.co/imprint', 167 | }, 168 | { 169 | label: 'OpenAPI Initiative', 170 | href: 'https://www.openapis.org/', 171 | }, 172 | { 173 | label: 'Open Collective', 174 | href: 'https://opencollective.com/openapi-stack', 175 | }, 176 | { 177 | label: 'Buy me a coffee', 178 | href: 'https://www.buymeacoffee.com/anttiviljami', 179 | }, 180 | ], 181 | }, 182 | ], 183 | copyright: Footer, 184 | }, 185 | prism: { 186 | theme: lightCodeTheme, 187 | darkTheme: darkCodeTheme, 188 | }, 189 | }), 190 | plugins: [ 191 | require.resolve('@cmfcmf/docusaurus-search-local'), 192 | async function tailwind(context, options) { 193 | return { 194 | name: "docusaurus-tailwindcss", 195 | configurePostCss(postcssOptions) { 196 | postcssOptions.plugins.push(require("tailwindcss")); 197 | postcssOptions.plugins.push(require("autoprefixer")); 198 | return postcssOptions; 199 | }, 200 | }; 201 | }, 202 | ], 203 | }; 204 | 205 | module.exports = config; 206 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openapi-stack", 3 | "version": "0.1.5", 4 | "description": "Full stack typesafe API-first development with OpenAPI", 5 | "author": "Viljami Kuosmanen ", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "keywords": [ 10 | "openapi", 11 | "rest", 12 | "typescript" 13 | ], 14 | "homepage": "https://openapistack.co", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/openapistack/docs.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/openapistack/docs/issues" 21 | }, 22 | "scripts": { 23 | "docusaurus": "docusaurus", 24 | "start": "docusaurus start", 25 | "build": "docusaurus build", 26 | "swizzle": "docusaurus swizzle", 27 | "deploy": "docusaurus deploy", 28 | "clear": "docusaurus clear", 29 | "serve": "docusaurus serve", 30 | "write-translations": "docusaurus write-translations", 31 | "write-heading-ids": "docusaurus write-heading-ids", 32 | "typecheck": "tsc" 33 | }, 34 | "dependencies": { 35 | "@docusaurus/core": "2.3.1", 36 | "@docusaurus/preset-classic": "2.3.1", 37 | "@mdx-js/react": "^1.6.22", 38 | "clsx": "^1.2.1", 39 | "prism-react-renderer": "^1.3.5", 40 | "react": "^17.0.2", 41 | "react-dom": "^17.0.2", 42 | "react-icons": "^4.11.0" 43 | }, 44 | "devDependencies": { 45 | "@cmfcmf/docusaurus-search-local": "^1.0.0", 46 | "@docusaurus/module-type-aliases": "2.3.1", 47 | "@tsconfig/docusaurus": "^1.0.5", 48 | "autoprefixer": "^10.4.13", 49 | "postcss": "^8.4.21", 50 | "tailwindcss": "^3.2.7", 51 | "typescript": "^4.7.4" 52 | }, 53 | "browserslist": { 54 | "production": [ 55 | ">0.5%", 56 | "not dead", 57 | "not op_mini all" 58 | ], 59 | "development": [ 60 | "last 1 chrome version", 61 | "last 1 firefox version", 62 | "last 1 safari version" 63 | ] 64 | }, 65 | "engines": { 66 | "node": ">=16.14" 67 | } 68 | } -------------------------------------------------------------------------------- /sidebars.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 3 | module.exports = { tutorialSidebar: [{type: 'autogenerated', dirName: '.'}] }; -------------------------------------------------------------------------------- /src/components/Comparisons.mdx: -------------------------------------------------------------------------------- 1 | export const GraphQLComparison = () => ( 2 |
3 | How does openapi-stack compare to GraphQL? 4 | 5 |
6 | ) 7 | -------------------------------------------------------------------------------- /src/components/HomepageFeatures/GithubStarsButton.tsx: -------------------------------------------------------------------------------- 1 | import Link from '@docusaurus/Link'; 2 | import React, { useEffect, useMemo, useState } from 'react'; 3 | 4 | const REPOSITORIES = [ 5 | 'openapistack/docs', 6 | 'openapistack/openapi-backend', 7 | 'openapistack/openapi-client-axios', 8 | 'openapistack/openapicmd', 9 | ] 10 | 11 | export const GithubStarsButton = () => { 12 | const [starsMap, setStarsMap] = useState<{ [key: string]: number }>({}); 13 | 14 | const fetchStars = async () => { 15 | return await Promise.all( 16 | REPOSITORIES.map(async (repo) => { 17 | const res = await fetch(['https://api.github.com/repos', repo].join('/')) 18 | 19 | const data = (await res.json()) as { stargazers_count: number }; 20 | 21 | if (typeof data?.stargazers_count === 'number') { 22 | setStarsMap((prev) => ({ ...prev, [repo]: data.stargazers_count })); 23 | } 24 | }) 25 | ); 26 | }; 27 | 28 | useEffect(() => { 29 | fetchStars().catch(console.error); 30 | }, []); 31 | 32 | const stars = useMemo(() => { 33 | return Object.values(starsMap) 34 | .reduce((acc, curr) => acc + curr, 0); 35 | }, [starsMap]) 36 | 37 | return ( 38 | 42 | 43 | Star 44 | 45 | {stars ? `(${stars})` : '...'} 46 | 47 | 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Link from '@docusaurus/Link'; 4 | 5 | type FeatureItem = { 6 | title: string; 7 | Svg: React.ComponentType>; 8 | description: JSX.Element; 9 | }; 10 | 11 | const FeatureList: FeatureItem[] = [ 12 | { 13 | title: 'Backend', 14 | Svg: require('@site/static/img/undraw_secure_login_pdn4.svg').default, 15 | description: ( 16 | <> 17 | Build, Validate, Route, Authenticate, and Mock your backend using the openapi-backend library. 18 | 19 | ), 20 | }, 21 | { 22 | title: 'Client', 23 | Svg: require('@site/static/img/undraw_code_inspection_bdl7.svg').default, 24 | description: ( 25 | <> 26 | Easily consume your OpenAPI specification using the typesafe openapi-client-axios library. 27 | 28 | ), 29 | }, 30 | { 31 | title: 'CLI', 32 | Svg: require('@site/static/img/undraw_developer_activity_re_39tg.svg').default, 33 | description: ( 34 | <> 35 | Generate types, design and test your API using the openapicmd command line tool. 36 | 37 | ), 38 | }, 39 | ]; 40 | 41 | function Feature({title, Svg, description}: FeatureItem) { 42 | return ( 43 |
44 |
45 | 46 |
47 | 48 |
49 |

{title}

50 |

{description}

51 |
52 |
53 | ); 54 | } 55 | 56 | export default function HomepageFeatures(): JSX.Element { 57 | return ( 58 |
59 |
60 |
61 | {FeatureList.map((props, idx) => ( 62 | 63 | ))} 64 |
65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/components/Sandbox/Iframe.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import React, { ComponentPropsWithoutRef, useState } from 'react'; 3 | 4 | export const Iframe = ( 5 | props: Omit, 'className'>, 6 | ) => { 7 | const [loaded, setLoaded] = useState(false); 8 | return ( 9 |