From 4ab8c66f5fa721d4598d9e82840c294eb10a50cd Mon Sep 17 00:00:00 2001 From: Abdul Baari Davids Date: Mon, 26 May 2025 01:21:52 +0200 Subject: [PATCH 01/11] add refresh --- openapi/frameworks/zod.mdx | 487 +++++++++++++++++++++++++++++++------ 1 file changed, 418 insertions(+), 69 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index d6c47c44..8a2bf0b4 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -12,19 +12,21 @@ Zod is a powerful and flexible schema validation library for TypeScript. Many us In this tutorial, we'll take a detailed look at how to set up Zod OpenAPI to generate an OpenAPI schema based on Zod schemas. Then we'll use Speakeasy to read our generated OpenAPI schema and generate a production-ready client SDK. -## An Example Schema: Burgers and Orders +## Why Zod + OpenAPI? -We'll start with a tiny example schema describing two main types: Burgers and Orders. A burger is a menu item with an ID, name, and description. An order has an ID, a non-empty list of burger IDs, the time the order was placed, a table number, a status, and an optional note for the kitchen. +Combining Zod with OpenAPI generation offers the best of both worlds: runtime validation and automatic API documentation. Instead of writing schemas twice—once for runtime validation and again for your OpenAPI spec—you define your data models once in Zod and generate both TypeScript types and OpenAPI documentation from the same source. -Anticipating our CRUD app, we'll also add additional schemas describing fields for creating new objects without IDs or updating existing objects where all fields are optional. +This eliminates the task of keeping hand-written OpenAPI specs in sync with your actual API implementation. When paired with Speakeasy's SDK generation, you get type-safe client libraries that automatically stay up-to-date with your API changes. - - If you want to follow along with the example code in this tutorial, you can - clone the [Speakeasy Zod OpenAPI example - repo](https://github.com/speakeasy-api/speakeasy-zod-openapi). + + +This guide uses [zod-openapi](https://github.com/samchungy/zod-openapi), which is actively maintained and offers better TypeScript integration than the older zod-to-openapi library. Currently, zod-openapi requires Zod v3 compatibility, which is why we use a dual import strategy throughout this tutorial. + +While Zod v4 introduces new features like `z.strictObject()` and `z.email()`, you'll need to use the standard Zod import for OpenAPI schemas and the `/v4` subpath for new features until zod-openapi adds full v4 support. Check the [zod-openapi releases](https://github.com/samchungy/zod-openapi/releases) for the latest compatibility updates. -## An Overview of Zod OpenAPI + +## Zod OpenAPI Overview [Zod OpenAPI](https://github.com/samchungy/zod-openapi) is a TypeScript library that helps developers define OpenAPI schemas as Zod schemas. The stated goal of the project is to cut down on code duplication, and it does a wonderful job of this. @@ -32,59 +34,192 @@ Zod schemas map to OpenAPI schemas well, and the changes required to extract Ope Zod OpenAPI is maintained by one of the contributors to an earlier library called [Zod to OpenAPI](https://github.com/asteasolutions/zod-to-openapi). If you already use Zod to OpenAPI, the syntax will be familiar and you should be able to use either library. If you'd like to convert your _Zod to OpenAPI_ code to _Zod OpenAPI_ code, the _Zod OpenAPI_ library provides helpful [documentation for migrating code](https://github.com/samchungy/zod-openapi#migration). +### Key Concepts + +#### Schemas & z.strictObject + + +This guide demonstrates a **dual import strategy** for using both Zod v3 and v4 in the same project. This approach is necessary because `zod-openapi` currently requires Zod v3 compatibility, while you may want to use Zod v4 features elsewhere in your application. + +Unlike most libraries, Zod is directly embedded in hundreds of other libraries' public APIs. A normal `zod@4.0.0` release would force every one of those libraries to publish breaking changes simultaneously—a massive "version avalanche." + +The subpath approach (inspired by Go modules) lets libraries support both versions with one dependency, providing a gradual migration path for the entire ecosystem. + +See [Colin's detailed explanation](https://github.com/colinhacks/zod/issues/4371) for the full technical reasoning. + + +Zod 4 introduces top-level helpers like `z.strictObject()` for objects that reject unknown keys and `z.email()` for email validation. + +```typescript concept.ts +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; + +// Use z4 for new Zod v4 features +const user = z4.strictObject({ + email: z4.email(), + age: z4.number().int().min(18) +}); +``` + +#### Field metadata + +The `.openapi()` method adds OpenAPI-specific metadata like descriptions and examples to any Zod schema. Use z3 for OpenAPI schemas. + +```typescript concept.ts +import { z as z3 } from "zod"; +import { extendZodWithOpenApi } from "zod-openapi"; +extendZodWithOpenApi(z3); + +const name = z3.string().min(1).openapi({ + description: "The user's full name", + example: "Alice Johnson" +}); +``` + +#### Operation objects + +Use `ZodOpenApiOperationObject` to define API endpoints with request/response schemas. + +```typescript concept.ts +import { z as z3 } from "zod"; +import { ZodOpenApiOperationObject } from "zod-openapi"; + +const getUser: ZodOpenApiOperationObject = { + operationId: "getUser", + summary: "Get user by ID", + requestParams: { path: z3.object({ id: z3.string() }) }, + responses: { + "200": { + description: "User found", + content: { "application/json": { schema: userSchema } } + } + } +}; +``` + +#### Speakeasy extensions + +Add SDK-specific behavior using `x-speakeasy-*` extensions in your OpenAPI spec. + +```typescript concept.ts +const config = { + info: { title: "My API", version: "1.0.0" }, + "x-speakeasy-retries": { + strategy: "backoff", + backoff: { initialInterval: 500, maxInterval: 60000 } + } +}; +``` + +#### Using Zod v4 features alongside OpenAPI schemas + +While your OpenAPI schemas must use the z3 instance for compatibility, you can use Zod v4 features for internal validation, type checking, or other parts of your application: + +```typescript concept.ts +// OpenAPI-compatible schemas (use z3) +const apiUserSchema = z3.object({ + id: z3.string(), + name: z3.string(), + email: z3.string() +}).openapi('User'); + +// Internal schemas can use Zod v4 features (use z4) +const internalUserSchema = z4.strictObject({ // v4 feature + id: z4.string().uuid(), + name: z4.string().min(1), + email: z4.string().email(), // v4 feature + preferences: z4.object({ + darkMode: z4.boolean(), + notifications: z4.enum(["all", "mentions", "none"]) + }).optional() +}); +``` + ## Step-by-Step Tutorial: From Zod to OpenAPI to an SDK Now let's walk through the process of generating an OpenAPI schema and SDK for our Burgers and Orders API. +## Requirements + +This tutorial assumes basic familiarity with TypeScript and Node.js development. + +The following should be installed on your machine: + +- [Node.js version 18 or above](https://nodejs.org/en/download) +- The [Speakeasy CLI](/docs/introduction#install-the-speakeasy-cli), which we'll use to generate an SDK from the OpenAPI document + ### 1. Create Your Zod Project -If you would like to follow along, start by creating a new directory for your project. We'll call ours `zod-burgers`. + +The source code for our complete example is available in the [`speakeasy-api/examples`](https://github.com/speakeasy-api/examples.git) repository. The project contains a pre-generated Python SDK with instructions on how to generate more SDKs. You can clone this repository to test how changes to the Zod schema definition result in changes to the generated SDK. + + -Then, initialize a new npm project and install Zod: +Alternatively, you can initialize a new npm project and install the required dependencies if you're not using our burgers example. -```bash Terminal -mkdir zod-burgers -cd zod-burgers +``` npm init -y -npm install zod +npm install zod@^3.25 zod-openapi yaml ``` -### 2. Install the Zod OpenAPI Library +If you're following along, start by cloning the `speakeasy-api/examples` repository. -Use npm to install `zod-openapi`: +```bash Terminal +git clone https://github.com/speakeasy-api/examples.git +cd zod-openapi +``` + +Next, install the dependencies: ```bash Terminal -npm install zod-openapi yaml +npm install +``` + +### 2. Install TypeScript Development Tools + +For this tutorial, we'll use `tsx` for running TypeScript directly: + +```bash Terminal +npm install -D tsx ``` ### 3. Create Your App's First Zod Schema -Save this TypeScript code in a new file called `index.ts`. +Save this TypeScript code in a new file called `index.ts`. Note the dual import strategy: ```typescript index.ts -import { z } from "zod"; -const burgerSchema = z.object({ - id: z.number().min(1), - name: z.string().min(1).max(50), - description: z.string().max(255).optional(), + +// Import Zod v3 compatible instance for zod-openapi +import { z as z3 } from "zod"; +// Import Zod v4 for new features and future migration +import { z as z4 } from "zod/v4"; + +// For now, we'll use z3 for OpenAPI schemas since zod-openapi requires it +const burgerSchema = z3.object({ + id: z3.number().min(1), + name: z3.string().min(1).max(50), + description: z3.string().max(255).optional(), }); ``` ### 4. Extend Zod With OpenAPI -We'll add the `openapi` method to Zod by calling `extendZodWithOpenApi` once. Update `index.ts` to import `extendZodWithOpenApi` from `zod-openapi`, then call `extendZodWithOpenApi`. +We'll add the `openapi` method to Zod by calling `extendZodWithOpenApi` once. Update `index.ts` to import `extendZodWithOpenApi` from `zod-openapi`, then call `extendZodWithOpenApi` on the z3 instance. ```typescript index.ts -import { z } from "zod"; - +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi } from "zod-openapi"; -extendZodWithOpenApi(z); -const burgerSchema = z.object({ - id: z.number().min(1), - name: z.string().min(1).max(50), - description: z.string().max(255).optional(), +// Extend the Zod v3 compatible instance for zod-openapi +extendZodWithOpenApi(z3); + +// Schemas defined with z3 for current zod-openapi compatibility +const burgerSchema = z3.object({ + id: z3.number().min(1), + name: z3.string().min(1).max(50), + description: z3.string().max(255).optional(), }); ``` @@ -95,15 +230,16 @@ Next, we'll use the new `openapi` method provided by `extendZodWithOpenApi` to r We'll also add an OpenAPI generator, `OpenApiGeneratorV31`, and log the generated component to the console as YAML. ```typescript index.ts -import { z } from "zod"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi } from "zod-openapi"; -extendZodWithOpenApi(z); +extendZodWithOpenApi(z3); -const burgerSchema = z.object({ - id: z.number().min(1), - name: z.string().min(1).max(50), - description: z.string().max(255).optional(), +const burgerSchema = z3.object({ + id: z3.number().min(1), + name: z3.string().min(1).max(50), + description: z3.string().max(255).optional(), }); burgerSchema.openapi({ ref: "Burger" }); @@ -122,21 +258,22 @@ We'll also add a description to the `Burger` component itself. Edit `index.ts` and edit `burgerSchema` to add OpenAPI metadata. ```typescript index.ts -import { z } from "zod"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi } from "zod-openapi"; -extendZodWithOpenApi(z); +extendZodWithOpenApi(z3); -const burgerSchema = z.object({ - id: z.number().min(1).openapi({ +const burgerSchema = z3.object({ + id: z3.number().min(1).openapi({ description: "The unique identifier of the burger.", example: 1, }), - name: z.string().min(1).max(50).openapi({ + name: z3.string().min(1).max(50).openapi({ description: "The name of the burger.", example: "Veggie Burger", }), - description: z.string().max(255).optional().openapi({ + description: z3.string().max(255).optional().openapi({ description: "The description of the burger.", example: "A delicious bean burger with avocado.", }), @@ -157,7 +294,7 @@ Import `yaml` and `createDocument`. ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod"; +import { z } from "zod/v4"; import { extendZodWithOpenApi, @@ -193,7 +330,7 @@ We'll use the `createDocument` method to generate an OpenAPI document. We'll pas ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod"; +import { z } from "zod/v4"; import { extendZodWithOpenApi, @@ -252,7 +389,7 @@ Run the code in the terminal: openapi: 3.1.0 info: title: Burger Restaurant API - description: An API for managing burgers at a restaurant. + description: An API for managing burgers and orders at a restaurant. version: 1.0.0 servers: - url: https://example.com @@ -285,7 +422,7 @@ components: ``` ```bash Terminal -npx ts-node index.ts +npx tsx index.ts ``` ### 10. Add a Burger ID Schema @@ -297,7 +434,7 @@ Let's create the burger ID schema now. ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod"; +import { z } from "zod/v4"; import { extendZodWithOpenApi, @@ -367,7 +504,7 @@ We'll replace the burger ID field with a reference to the burger ID schema. ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod"; +import { z } from "zod/v4"; import { extendZodWithOpenApi, @@ -437,7 +574,7 @@ We'll add a schema for creating burgers that doesn't include an ID. We'll use th ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod"; +import { z } from "zod/v4"; import { extendZodWithOpenApi, @@ -515,7 +652,7 @@ We'll use `ZodOpenApiOperationObject` from `zod-openapi` to add `create burger`, ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod"; +import { z } from "zod/v4"; import { extendZodWithOpenApi, @@ -632,7 +769,7 @@ We'll add a webhook that runs when a burger is created. We'll use the `ZodOpenAp ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod"; +import { z } from "zod/v4"; import { extendZodWithOpenApi, @@ -727,7 +864,7 @@ We'll register our paths and webhooks by adding them to the document definition. ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod"; +import { z } from "zod/v4"; import { extendZodWithOpenApi, @@ -775,25 +912,43 @@ const document = createDocument({ console.log(yaml.stringify(document)); ``` -### 16. Run the Code +### 16. Generate the OpenAPI Schema -Run the code in the terminal: +Run the `index.ts` file to generate the OpenAPI schema. + +```bash Terminal +npx tsx index.ts > openapi.yaml +``` + +The output will be a YAML file that looks like this: ```yaml !! Output openapi: 3.1.0 info: title: Burger Restaurant API - description: An API for managing burgers at a restaurant. + description: An API for managing burgers and orders at a restaurant. version: 1.0.0 servers: - url: https://example.com description: The production server. +x-speakeasy-retries: + strategy: backoff + backoff: + initialInterval: 500 + maxInterval: 60000 + maxElapsedTime: 3600000 + exponent: 1.5 + statusCodes: + - 5XX + retryConnectionErrors: true paths: /burgers: post: operationId: createBurger summary: Create a new burger description: Creates a new burger in the database. + tags: + - burgers requestBody: description: The burger to create. content: @@ -807,14 +962,32 @@ paths: application/json: schema: $ref: "#/components/schemas/burgerSchema" - "/burgers/{id}": + get: + operationId: listBurgers + summary: List burgers + description: Lists all burgers in the database. + tags: + - burgers + responses: + "200": + description: The burgers were retrieved successfully. + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/burgerSchema" + /burgers/{id}: get: operationId: getBurger summary: Get a burger description: Gets a burger from the database. + tags: + - burgers parameters: - in: path name: id + description: The unique identifier of the burger. schema: $ref: "#/components/schemas/BurgerId" required: true @@ -825,12 +998,55 @@ paths: application/json: schema: $ref: "#/components/schemas/burgerSchema" + /orders: + post: + operationId: createOrder + summary: Create a new order + description: Creates a new order in the database. + tags: + - orders + requestBody: + description: The order to create. + content: + application/json: + schema: + $ref: "#/components/schemas/OrderCreate" + responses: + "201": + description: The order was created successfully. + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + /orders/{id}: + get: + operationId: getOrder + summary: Get an order + description: Gets an order from the database. + tags: + - orders + parameters: + - in: path + name: id + description: The unique identifier of the order. + schema: + $ref: "#/components/schemas/OrderId" + required: true + responses: + "200": + description: The order was retrieved successfully. + content: + application/json: + schema: + $ref: "#/components/schemas/Order" webhooks: /burgers: post: operationId: createBurgerWebhook summary: New burger webhook description: A webhook that is called when a new burger is created. + tags: + - burgers requestBody: description: The burger that was created. content: @@ -883,42 +1099,175 @@ components: minimum: 1 description: The unique identifier of the burger. example: 1 + Order: + type: object + properties: + id: + $ref: "#/components/schemas/OrderId" + burger_ids: + type: array + items: + $ref: "#/components/schemas/BurgerId" + minItems: 1 + description: The burgers in the order. + example: &a1 + - 1 + - 2 + time: + type: string + format: date-time + description: The time the order was placed. + example: 2021-01-01T00:00:00.000Z + table: + type: number + minimum: 1 + description: The table the order is for. + example: 1 + status: + type: string + enum: &a2 + - pending + - in_progress + - ready + - delivered + description: The status of the order. + example: pending + note: + type: string + description: A note for the order. + example: No onions. + required: + - id + - burger_ids + - time + - table + - status + description: An order placed at the restaurant. + OrderCreate: + type: object + properties: + burger_ids: + type: array + items: + $ref: "#/components/schemas/BurgerId" + minItems: 1 + description: The burgers in the order. + example: *a1 + time: + type: string + format: date-time + description: The time the order was placed. + example: 2021-01-01T00:00:00.000Z + table: + type: number + minimum: 1 + description: The table the order is for. + example: 1 + status: + type: string + enum: *a2 + description: The status of the order. + example: pending + note: + type: string + description: A note for the order. + example: No onions. + required: + - burger_ids + - time + - table + - status + description: An order to create. + OrderId: + type: number + minimum: 1 + description: The unique identifier of the order. + example: 1 + + + ``` +### 17. Generate an SDK + +With our OpenAPI schema complete, we can now generate an SDK using the Speakeasy SDK generator. + +### Install the Speakeasy CLI + +First, install the Speakeasy CLI: + ```bash Terminal -npx ts-node index.ts +# Using Homebrew (recommended) +brew install speakeasy-api/tap/speakeasy + +# Using curl +curl -fsSL https://go.speakeasy.com/cli-install.sh | sh ``` -### 17. Generate an SDK +### Lint Your OpenAPI Spec -With our OpenAPI schema complete, we can now generate an SDK using the Speakeasy SDK generator. We'll follow the instructions in the Speakeasy documentation to [generate SDKs for various platforms](/docs/create-client-sdks). +Before generating SDKs, lint your spec to catch common issues: -First, write your YAML schema to a new file called `openapi.yaml`. Run the following in the terminal: +```bash Terminal +speakeasy lint openapi --schema openapi.yaml +``` + +### Use AI to Improve Your Spec + +The Speakeasy CLI now includes AI-powered suggestions to automatically improve your OpenAPI specifications: ```bash Terminal -npx ts-node index.ts > openapi.yaml +speakeasy suggest openapi.yaml ``` -Then, log in to your Speakeasy account or use the [Speakeasy CLI](/docs/speakeasy-cli/getting-started/) to generate a new SDK. +Follow the onscreen prompts to provide the necessary configuration details for your new SDK such as the schema and output path. + +Read the [Speakeasy Suggest](/docs/prep-openapi/maintenance) documentation for more information on how to use Speakeasy Suggest. + +### Generate Your SDK -Here's how to use the CLI. In the terminal, run: +Now you can generate your SDK using the quickstart command: ```bash Terminal speakeasy quickstart ``` -Follow the onscreen prompts to provide the necessary configuration details for your new SDK such as the name, schema location and output path. Enter `openapi.yaml` when prompted for the OpenAPI document location and select Python when prompted for which language you would like to generate. +Follow the onscreen prompts to provide the necessary configuration details for your new SDK such as the name, schema location and output path. Enter `openapi.yaml` (or your improved spec if you used suggestions) when prompted for the OpenAPI document location and select your preferred language when prompted. -## Example Zod Schema and SDK Generator +## Using Your Generated SDK -The source code for our complete example is available in the [`zod-burgers`](https://github.com/speakeasy-api/speakeasy-zod-openapi) repository. +Once you've generated your SDK, you can [publish](/docs/publish-sdk) it for use. For TypeScript, you can publish it as an npm package. -The repository contains a pre-generated Python SDK with instructions on how to generate more SDKs. +TypeScript SDKs generated with Speakeasy include an installable [Model Context Protocol (MCP) server](/docs/model-context-protocol) where the various SDK methods are exposed as tools that can be invoked by AI applications. Your SDK documentation includes instructions for installing the MCP server. + + +Note that the SDK is not ready for production use immediately after generation. To get it production-ready, follow the steps outlined in your Speakeasy workspace. + + +## Adding SDK Generation to Your CI/CD Pipeline + +The Speakeasy [`sdk-generation-action`](https://github.com/speakeasy-api/sdk-generation-action) repository provides workflows for integrating the Speakeasy CLI into CI/CD pipelines to automatically regenerate SDKs when your Zod schemas change. + +You can set up Speakeasy to automatically push a new branch to your SDK repositories so that your engineers can review and merge the SDK changes. + +For an overview of how to set up automation for your SDKs, see the Speakeasy [SDK Workflow syntax reference](/docs/speakeasy-reference/workflow-file). -You can clone this repository to test how changes to the Zod schema definition result in changes to the generated SDK. ## Summary In this tutorial, we learned how to generate OpenAPI schemas from Zod and create client SDKs with Speakeasy. By following these steps, you can ensure that your API is well-documented, easy to use, and offers a great developer experience. + +## Further Reading + +This guide covered the basics of generating an OpenAPI document using Zod OpenAPI. Here are some resources to help you learn more about Zod, OpenAPI, and Speakeasy: + +- [Zod OpenAPI documentation](https://github.com/samchungy/zod-openapi): Learn more about the zod-openapi library, including advanced features like custom serializers and middleware integration. +- [Zod documentation](https://zod.dev/): Comprehensive guide to Zod schema validation, including the latest v4 features. +- [Speakeasy documentation](/docs): Speakeasy has extensive documentation covering how to generate SDKs from OpenAPI documents, customize SDKs, and more. +- [Speakeasy OpenAPI reference](/openapi): View a detailed reference for the OpenAPI Specification. +- [Speakeasy Blog](/blog): Read articles about OpenAPI, SDK generation, and more including: + - [Native JSONL support in your SDKs](/blog/release-jsonl-support) + - [Introducing comprehensive SDK Testing](/blog/release-sdk-testing) + - [Model Context Protocol: TypeScript SDKs for the Agentic AI ecosystem](/blog/release-model-context-protocol) From 60ecf3ca9303d5251887adaa230543ba600a8085 Mon Sep 17 00:00:00 2001 From: Abdul Baari Davids Date: Mon, 26 May 2025 01:25:32 +0200 Subject: [PATCH 02/11] links --- openapi/frameworks/zod.mdx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index 8a2bf0b4..eec22cda 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -1265,9 +1265,4 @@ This guide covered the basics of generating an OpenAPI document using Zod OpenAP - [Zod OpenAPI documentation](https://github.com/samchungy/zod-openapi): Learn more about the zod-openapi library, including advanced features like custom serializers and middleware integration. - [Zod documentation](https://zod.dev/): Comprehensive guide to Zod schema validation, including the latest v4 features. -- [Speakeasy documentation](/docs): Speakeasy has extensive documentation covering how to generate SDKs from OpenAPI documents, customize SDKs, and more. -- [Speakeasy OpenAPI reference](/openapi): View a detailed reference for the OpenAPI Specification. -- [Speakeasy Blog](/blog): Read articles about OpenAPI, SDK generation, and more including: - - [Native JSONL support in your SDKs](/blog/release-jsonl-support) - - [Introducing comprehensive SDK Testing](/blog/release-sdk-testing) - - [Model Context Protocol: TypeScript SDKs for the Agentic AI ecosystem](/blog/release-model-context-protocol) + From 85c05ddaea553a6d00b848ced8d726fac2ebd392 Mon Sep 17 00:00:00 2001 From: Abdul Baari Davids Date: Mon, 26 May 2025 14:35:21 +0200 Subject: [PATCH 03/11] fix z3 dual import --- openapi/frameworks/zod.mdx | 99 +++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index eec22cda..d413beb4 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -294,24 +294,25 @@ Import `yaml` and `createDocument`. ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi, createDocument } from "zod-openapi"; -extendZodWithOpenApi(z); +extendZodWithOpenApi(z3); -const burgerSchema = z.object({ - id: z.number().min(1).openapi({ +const burgerSchema = z3.object({ + id: z3.number().min(1).openapi({ description: "The unique identifier of the burger.", example: 1, }), - name: z.string().min(1).max(50).openapi({ + name: z3.string().min(1).max(50).openapi({ description: "The name of the burger.", example: "Veggie Burger", }), - description: z.string().max(255).optional().openapi({ + description: z3.string().max(255).optional().openapi({ description: "The description of the burger.", example: "A delicious bean burger with avocado.", }), @@ -330,24 +331,25 @@ We'll use the `createDocument` method to generate an OpenAPI document. We'll pas ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi, createDocument } from "zod-openapi"; -extendZodWithOpenApi(z); +extendZodWithOpenApi(z3); -const burgerSchema = z.object({ - id: z.number().min(1).openapi({ +const burgerSchema = z3.object({ + id: z3.number().min(1).openapi({ description: "The unique identifier of the burger.", example: 1, }), - name: z.string().min(1).max(50).openapi({ + name: z3.string().min(1).max(50).openapi({ description: "The name of the burger.", example: "Veggie Burger", }), - description: z.string().max(255).optional().openapi({ + description: z3.string().max(255).optional().openapi({ description: "The description of the burger.", example: "A delicious bean burger with avocado.", }), @@ -434,15 +436,16 @@ Let's create the burger ID schema now. ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi, createDocument } from "zod-openapi"; -extendZodWithOpenApi(z); +extendZodWithOpenApi(z3); -const BurgerIdSchema = z +const BurgerIdSchema = z3 .number() .min(1) .openapi({ @@ -455,13 +458,13 @@ const BurgerIdSchema = z }, }); -const burgerSchema = z.object({ +const burgerSchema = z3.object({ id: BurgerIdSchema, - name: z.string().min(1).max(50).openapi({ + name: z3.string().min(1).max(50).openapi({ description: "The name of the burger.", example: "Veggie Burger", }), - description: z.string().max(255).optional().openapi({ + description: z3.string().max(255).optional().openapi({ description: "The description of the burger.", example: "A delicious bean burger with avocado.", }), @@ -504,15 +507,16 @@ We'll replace the burger ID field with a reference to the burger ID schema. ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi, createDocument } from "zod-openapi"; -extendZodWithOpenApi(z); +extendZodWithOpenApi(z3); -const BurgerIdSchema = z +const BurgerIdSchema = z3 .number() .min(1) .openapi({ @@ -525,13 +529,13 @@ const BurgerIdSchema = z }, }); -const burgerSchema = z.object({ +const burgerSchema = z3.object({ id: BurgerIdSchema, - name: z.string().min(1).max(50).openapi({ + name: z3.string().min(1).max(50).openapi({ description: "The name of the burger.", example: "Veggie Burger", }), - description: z.string().max(255).optional().openapi({ + description: z3.string().max(255).optional().openapi({ description: "The description of the burger.", example: "A delicious bean burger with avocado.", }), @@ -574,15 +578,16 @@ We'll add a schema for creating burgers that doesn't include an ID. We'll use th ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi, createDocument } from "zod-openapi"; -extendZodWithOpenApi(z); +extendZodWithOpenApi(z3); -const BurgerIdSchema = z +const BurgerIdSchema = z3 .number() .min(1) .openapi({ @@ -595,13 +600,13 @@ const BurgerIdSchema = z }, }); -const burgerSchema = z.object({ +const burgerSchema = z3.object({ id: BurgerIdSchema, - name: z.string().min(1).max(50).openapi({ + name: z3.string().min(1).max(50).openapi({ description: "The name of the burger.", example: "Veggie Burger", }), - description: z.string().max(255).optional().openapi({ + description: z3.string().max(255).optional().openapi({ description: "The description of the burger.", example: "A delicious bean burger with avocado.", }), @@ -652,16 +657,17 @@ We'll use `ZodOpenApiOperationObject` from `zod-openapi` to add `create burger`, ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi, ZodOpenApiOperationObject, createDocument } from "zod-openapi"; -extendZodWithOpenApi(z); +extendZodWithOpenApi(z3); -const BurgerIdSchema = z +const BurgerIdSchema = z3 .number() .min(1) .openapi({ @@ -674,13 +680,13 @@ const BurgerIdSchema = z }, }); -const burgerSchema = z.object({ +const burgerSchema = z3.object({ id: BurgerIdSchema, - name: z.string().min(1).max(50).openapi({ + name: z3.string().min(1).max(50).openapi({ description: "The name of the burger.", example: "Veggie Burger", }), - description: z.string().max(255).optional().openapi({ + description: z3.string().max(255).optional().openapi({ description: "The description of the burger.", example: "A delicious bean burger with avocado.", }), @@ -725,7 +731,7 @@ const getBurger: ZodOpenApiOperationObject = { summary: "Get a burger", description: "Gets a burger from the database.", requestParams: { - path: z.object({ id: BurgerIdSchema }), + path: z3.object({ id: BurgerIdSchema }), }, responses: { "200": { @@ -769,16 +775,18 @@ We'll add a webhook that runs when a burger is created. We'll use the `ZodOpenAp ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi, ZodOpenApiOperationObject, createDocument } from "zod-openapi"; -extendZodWithOpenApi(z); +// Extend the Zod v3 compatible instance for zod-openapi +extendZodWithOpenApi(z3); -const BurgerIdSchema = z +const BurgerIdSchema = z3 .number() .min(1) .openapi({ @@ -791,13 +799,13 @@ const BurgerIdSchema = z }, }); -const burgerSchema = z.object({ +const burgerSchema = z3.object({ id: BurgerIdSchema, - name: z.string().min(1).max(50).openapi({ + name: z3.string().min(1).max(50).openapi({ description: "The name of the burger.", example: "Veggie Burger", }), - description: z.string().max(255).optional().openapi({ + description: z3.string().max(255).optional().openapi({ description: "The description of the burger.", example: "A delicious bean burger with avocado.", }), @@ -864,14 +872,15 @@ We'll register our paths and webhooks by adding them to the document definition. ```typescript index.ts import * as yaml from "yaml"; -import { z } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi, ZodOpenApiOperationObject, createDocument } from "zod-openapi"; -extendZodWithOpenApi(z); +extendZodWithOpenApi(z3); // Previous code from examples above... // BurgerIdSchema, burgerSchema, burgerCreateSchema, createBurger, getBurger, createBurgerWebhook From 2a6b8f564969f73984219009749dffab8528db70 Mon Sep 17 00:00:00 2001 From: Abdul Baari Davids Date: Mon, 26 May 2025 14:40:44 +0200 Subject: [PATCH 04/11] fix heading --- openapi/frameworks/zod.mdx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index d413beb4..1315b1b2 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -139,7 +139,7 @@ const internalUserSchema = z4.strictObject({ // v4 feature Now let's walk through the process of generating an OpenAPI schema and SDK for our Burgers and Orders API. -## Requirements +### Requirements This tutorial assumes basic familiarity with TypeScript and Node.js development. @@ -1192,16 +1192,13 @@ components: minimum: 1 description: The unique identifier of the order. example: 1 - - - ``` ### 17. Generate an SDK With our OpenAPI schema complete, we can now generate an SDK using the Speakeasy SDK generator. -### Install the Speakeasy CLI +#### Install the Speakeasy CLI First, install the Speakeasy CLI: @@ -1213,7 +1210,7 @@ brew install speakeasy-api/tap/speakeasy curl -fsSL https://go.speakeasy.com/cli-install.sh | sh ``` -### Lint Your OpenAPI Spec +#### Lint Your OpenAPI Spec Before generating SDKs, lint your spec to catch common issues: @@ -1221,7 +1218,7 @@ Before generating SDKs, lint your spec to catch common issues: speakeasy lint openapi --schema openapi.yaml ``` -### Use AI to Improve Your Spec +#### Use AI to Improve Your Spec The Speakeasy CLI now includes AI-powered suggestions to automatically improve your OpenAPI specifications: @@ -1233,7 +1230,7 @@ Follow the onscreen prompts to provide the necessary configuration details for y Read the [Speakeasy Suggest](/docs/prep-openapi/maintenance) documentation for more information on how to use Speakeasy Suggest. -### Generate Your SDK +#### Generate Your SDK Now you can generate your SDK using the quickstart command: @@ -1253,7 +1250,7 @@ TypeScript SDKs generated with Speakeasy include an installable [Model Context P Note that the SDK is not ready for production use immediately after generation. To get it production-ready, follow the steps outlined in your Speakeasy workspace. -## Adding SDK Generation to Your CI/CD Pipeline +### Adding SDK Generation to Your CI/CD Pipeline The Speakeasy [`sdk-generation-action`](https://github.com/speakeasy-api/sdk-generation-action) repository provides workflows for integrating the Speakeasy CLI into CI/CD pipelines to automatically regenerate SDKs when your Zod schemas change. @@ -1268,7 +1265,7 @@ In this tutorial, we learned how to generate OpenAPI schemas from Zod and create By following these steps, you can ensure that your API is well-documented, easy to use, and offers a great developer experience. -## Further Reading +### Further Reading This guide covered the basics of generating an OpenAPI document using Zod OpenAPI. Here are some resources to help you learn more about Zod, OpenAPI, and Speakeasy: From 71cbbb73bfcccc8b9f9e0d05f870932177f7b3ef Mon Sep 17 00:00:00 2001 From: tatenda Date: Tue, 27 May 2025 10:13:32 +0200 Subject: [PATCH 05/11] updates --- openapi/frameworks/zod.mdx | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index 1315b1b2..520fdcc9 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -20,13 +20,13 @@ This eliminates the task of keeping hand-written OpenAPI specs in sync with your -This guide uses [zod-openapi](https://github.com/samchungy/zod-openapi), which is actively maintained and offers better TypeScript integration than the older zod-to-openapi library. Currently, zod-openapi requires Zod v3 compatibility, which is why we use a dual import strategy throughout this tutorial. +This guide uses [zod-openapi](https://github.com/samchungy/zod-openapi), which is actively maintained and offers better TypeScript integration than the older zod-to-openapi library. Currently, zod-openapi requires Zod v3 compatibility, which is why we use a dual import strategy throughout this tutorial. While Zod v4 introduces new features like `z.strictObject()` and `z.email()`, you'll need to use the standard Zod import for OpenAPI schemas and the `/v4` subpath for new features until zod-openapi adds full v4 support. Check the [zod-openapi releases](https://github.com/samchungy/zod-openapi/releases) for the latest compatibility updates. -## Zod OpenAPI Overview +## Zod OpenAPI Overview [Zod OpenAPI](https://github.com/samchungy/zod-openapi) is a TypeScript library that helps developers define OpenAPI schemas as Zod schemas. The stated goal of the project is to cut down on code duplication, and it does a wonderful job of this. @@ -41,7 +41,7 @@ Zod OpenAPI is maintained by one of the contributors to an earlier library calle This guide demonstrates a **dual import strategy** for using both Zod v3 and v4 in the same project. This approach is necessary because `zod-openapi` currently requires Zod v3 compatibility, while you may want to use Zod v4 features elsewhere in your application. -Unlike most libraries, Zod is directly embedded in hundreds of other libraries' public APIs. A normal `zod@4.0.0` release would force every one of those libraries to publish breaking changes simultaneously—a massive "version avalanche." +Unlike most libraries, Zod is directly embedded in hundreds of other libraries' public APIs. A normal `zod@4.0.0` release would force every one of those libraries to publish breaking changes simultaneously—a massive "version avalanche." The subpath approach (inspired by Go modules) lets libraries support both versions with one dependency, providing a gradual migration path for the entire ecosystem. @@ -151,7 +151,7 @@ The following should be installed on your machine: ### 1. Create Your Zod Project -The source code for our complete example is available in the [`speakeasy-api/examples`](https://github.com/speakeasy-api/examples.git) repository. The project contains a pre-generated Python SDK with instructions on how to generate more SDKs. You can clone this repository to test how changes to the Zod schema definition result in changes to the generated SDK. +The source code for our complete example is available in the [`speakeasy-api/examples`](https://github.com/speakeasy-api/examples.git) repository in the `zod-openapi` directory. The project contains a pre-generated Python SDK with instructions on how to generate more SDKs. You can clone this repository to test how changes to the Zod schema definition result in changes to the generated SDK. @@ -191,9 +191,9 @@ Save this TypeScript code in a new file called `index.ts`. Note the dual import // Import Zod v3 compatible instance for zod-openapi -import { z as z3 } from "zod"; +import { z as z3 } from "zod"; // Import Zod v4 for new features and future migration -import { z as z4 } from "zod/v4"; +import { z as z4 } from "zod/v4"; // For now, we'll use z3 for OpenAPI schemas since zod-openapi requires it const burgerSchema = z3.object({ @@ -208,8 +208,8 @@ const burgerSchema = z3.object({ We'll add the `openapi` method to Zod by calling `extendZodWithOpenApi` once. Update `index.ts` to import `extendZodWithOpenApi` from `zod-openapi`, then call `extendZodWithOpenApi` on the z3 instance. ```typescript index.ts -import { z as z3 } from "zod"; -import { z as z4 } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi } from "zod-openapi"; // Extend the Zod v3 compatible instance for zod-openapi @@ -230,8 +230,8 @@ Next, we'll use the new `openapi` method provided by `extendZodWithOpenApi` to r We'll also add an OpenAPI generator, `OpenApiGeneratorV31`, and log the generated component to the console as YAML. ```typescript index.ts -import { z as z3 } from "zod"; -import { z as z4 } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi } from "zod-openapi"; extendZodWithOpenApi(z3); @@ -253,13 +253,11 @@ With Zod OpenAPI, we'll call the `.openapi` method on each field, and add an exa We'll also add a description to the `Burger` component itself. -{/* Speakeasy will generate documentation and usage examples based on the descriptions and examples we added, but first, we'll need to generate an OpenAPI schema. */} - Edit `index.ts` and edit `burgerSchema` to add OpenAPI metadata. ```typescript index.ts -import { z as z3 } from "zod"; -import { z as z4 } from "zod/v4"; +import { z as z3 } from "zod"; +import { z as z4 } from "zod/v4"; import { extendZodWithOpenApi } from "zod-openapi"; extendZodWithOpenApi(z3); @@ -385,20 +383,20 @@ console.log(yaml.stringify(document)); ### 9. Run the Code -Run the code in the terminal: +Run the code in the terminal with `npx tsx index.ts`: ```yaml !! Output openapi: 3.1.0 info: title: Burger Restaurant API - description: An API for managing burgers and orders at a restaurant. + description: An API for managing burgers at a restaurant. version: 1.0.0 servers: - url: https://example.com description: The production server. components: schemas: - Burger: + burgerSchema: type: object properties: id: @@ -420,12 +418,8 @@ components: required: - id - name - description: A burger served at the restaurant. ``` -```bash Terminal -npx tsx index.ts -``` ### 10. Add a Burger ID Schema From 6230f114bb0100c2ef6e861eb1a36fbb3f9a7aa1 Mon Sep 17 00:00:00 2001 From: Abdul Baari Davids Date: Tue, 27 May 2025 17:13:43 +0200 Subject: [PATCH 06/11] fix snippets --- openapi/frameworks/zod.mdx | 533 +++++++++++-------------------------- 1 file changed, 149 insertions(+), 384 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index 1315b1b2..58c84ccb 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -97,6 +97,19 @@ const getUser: ZodOpenApiOperationObject = { }; ``` +#### Adding Tags to Operations + +Tags help organize your API operations in the generated documentation and SDKs. You can add a `tags` array to each operation object: + +```typescript concept.ts +const getBurger: ZodOpenApiOperationObject = { + operationId: "getBurger", + summary: "Get a burger by ID", + tags: ["burgers"], // <--- Add tags here + // ...rest of the operation +}; +``` + #### Speakeasy extensions Add SDK-specific behavior using `x-speakeasy-*` extensions in your OpenAPI spec. @@ -188,8 +201,6 @@ npm install -D tsx Save this TypeScript code in a new file called `index.ts`. Note the dual import strategy: ```typescript index.ts - - // Import Zod v3 compatible instance for zod-openapi import { z as z3 } from "zod"; // Import Zod v4 for new features and future migration @@ -364,7 +375,7 @@ const document = createDocument({ openapi: "3.1.0", info: { title: "Burger Restaurant API", - description: "An API for managing burgers at a restaurant.", + description: "An API for managing burgers and orders at a restaurant.", version: "1.0.0", }, servers: [ @@ -376,6 +387,11 @@ const document = createDocument({ components: { schemas: { burgerSchema, + BurgerCreate, + BurgerId, + Order, + OrderCreate, + OrderId, }, }, }); @@ -383,68 +399,11 @@ const document = createDocument({ console.log(yaml.stringify(document)); ``` -### 9. Run the Code - -Run the code in the terminal: - -```yaml !! Output -openapi: 3.1.0 -info: - title: Burger Restaurant API - description: An API for managing burgers and orders at a restaurant. - version: 1.0.0 -servers: - - url: https://example.com - description: The production server. -components: - schemas: - Burger: - type: object - properties: - id: - type: number - minimum: 1 - description: The unique identifier of the burger. - example: 1 - name: - type: string - minLength: 1 - maxLength: 50 - description: The name of the burger. - example: Veggie Burger - description: - type: string - maxLength: 255 - description: The description of the burger. - example: A delicious bean burger with avocado. - required: - - id - - name - description: A burger served at the restaurant. -``` - -```bash Terminal -npx tsx index.ts -``` - -### 10. Add a Burger ID Schema +### 9. Add a Burger ID Schema To make the burger ID available to other schemas, we'll define a burger ID schema. We'll also use this schema to define a path parameter for the burger ID later on. -Let's create the burger ID schema now. - ```typescript index.ts -import * as yaml from "yaml"; - -import { z as z3 } from "zod"; -import { z as z4 } from "zod/v4"; - -import { - extendZodWithOpenApi, - createDocument -} from "zod-openapi"; -extendZodWithOpenApi(z3); - const BurgerIdSchema = z3 .number() .min(1) @@ -457,71 +416,30 @@ const BurgerIdSchema = z3 name: "id", }, }); +``` -const burgerSchema = z3.object({ - id: BurgerIdSchema, - name: z3.string().min(1).max(50).openapi({ - description: "The name of the burger.", - example: "Veggie Burger", - }), - description: z3.string().max(255).optional().openapi({ - description: "The description of the burger.", - example: "A delicious bean burger with avocado.", - }), -}); +### 10. Add a Schema for Creating Burgers -burgerSchema.openapi({ - ref: "Burger", - description: "A burger served at the restaurant.", -}); +We'll add a schema for creating burgers that doesn't include an ID. We'll use this schema to define the request body for the create burger path. -const document = createDocument({ - openapi: "3.1.0", - info: { - title: "Burger Restaurant API", - description: "An API for managing burgers at a restaurant.", - version: "1.0.0", - }, - servers: [ - { - url: "https://example.com", - description: "The production server.", - }, - ], - components: { - schemas: { - burgerSchema, - }, - }, +```typescript index.ts +const burgerCreateSchema = burgerSchema.omit({ id: true }).openapi({ + ref: "BurgerCreate", + description: "A burger to create.", }); - -console.log(yaml.stringify(document)); ``` ---- - -### 11. Replace the Burger ID Field With a Reference +### 11. Add Order Schemas and Operations -We'll replace the burger ID field with a reference to the burger ID schema. +To match the final OpenAPI output, let's add schemas and endpoints for orders. ```typescript index.ts -import * as yaml from "yaml"; - -import { z as z3 } from "zod"; -import { z as z4 } from "zod/v4"; - -import { - extendZodWithOpenApi, - createDocument -} from "zod-openapi"; -extendZodWithOpenApi(z3); - -const BurgerIdSchema = z3 +const OrderIdSchema = z3 .number() .min(1) .openapi({ - ref: "BurgerId", - description: "The unique identifier of the burger.", + ref: "OrderId", + description: "The unique identifier of the order.", example: 1, param: { in: "path", @@ -529,183 +447,52 @@ const BurgerIdSchema = z3 }, }); -const burgerSchema = z3.object({ - id: BurgerIdSchema, - name: z3.string().min(1).max(50).openapi({ - description: "The name of the burger.", - example: "Veggie Burger", +const orderSchema = z3.object({ + id: OrderIdSchema, + burger_ids: z3.array(BurgerIdSchema).min(1).openapi({ + description: "The burgers in the order.", + example: [1, 2], }), - description: z3.string().max(255).optional().openapi({ - description: "The description of the burger.", - example: "A delicious bean burger with avocado.", + time: z3.string().openapi({ + description: "The time the order was placed.", + example: "2021-01-01T00:00:00.000Z", + format: "date-time", }), -}); - -burgerSchema.openapi({ - ref: "Burger", - description: "A burger served at the restaurant.", -}); - -const document = createDocument({ - openapi: "3.1.0", - info: { - title: "Burger Restaurant API", - description: "An API for managing burgers at a restaurant.", - version: "1.0.0", - }, - servers: [ - { - url: "https://example.com", - description: "The production server.", - }, - ], - components: { - schemas: { - burgerSchema, - }, - }, -}); - -console.log(yaml.stringify(document)); -``` - ---- - -### 12. Add a Schema for Creating Burgers - -We'll add a schema for creating burgers that doesn't include an ID. We'll use this schema to define the request body for the create burger path. - -```typescript index.ts -import * as yaml from "yaml"; - -import { z as z3 } from "zod"; -import { z as z4 } from "zod/v4"; - -import { - extendZodWithOpenApi, - createDocument -} from "zod-openapi"; -extendZodWithOpenApi(z3); - -const BurgerIdSchema = z3 - .number() - .min(1) - .openapi({ - ref: "BurgerId", - description: "The unique identifier of the burger.", + table: z3.number().min(1).openapi({ + description: "The table the order is for.", example: 1, - param: { - in: "path", - name: "id", - }, - }); - -const burgerSchema = z3.object({ - id: BurgerIdSchema, - name: z3.string().min(1).max(50).openapi({ - description: "The name of the burger.", - example: "Veggie Burger", }), - description: z3.string().max(255).optional().openapi({ - description: "The description of the burger.", - example: "A delicious bean burger with avocado.", + status: z3.enum(["pending", "in_progress", "ready", "delivered"]).openapi({ + description: "The status of the order.", + example: "pending", }), + note: z3.string().openapi({ + description: "A note for the order.", + example: "No onions.", + }), +}).openapi({ + ref: "Order", + description: "An order placed at the restaurant.", }); -burgerSchema.openapi({ - ref: "Burger", - description: "A burger served at the restaurant.", -}); - -const burgerCreateSchema = burgerSchema.omit({ id: true }).openapi({ - ref: "BurgerCreate", - description: "A burger to create.", +const orderCreateSchema = orderSchema.omit({ id: true }).openapi({ + ref: "OrderCreate", + description: "An order to create.", }); - -const document = createDocument({ - openapi: "3.1.0", - info: { - title: "Burger Restaurant API", - description: "An API for managing burgers at a restaurant.", - version: "1.0.0", - }, - servers: [ - { - url: "https://example.com", - description: "The production server.", - }, - ], - components: { - schemas: { - burgerSchema, - }, - }, -}); - -console.log(yaml.stringify(document)); ``` -### 13. Add Paths - -Paths define the endpoints of your API. For our burger restaurant, we might define endpoints for creating, reading, updating, and deleting burgers and orders. - -To register paths and webhooks, we'll define paths as objects of type `ZodOpenApiOperationObject`, then add our paths and webhooks to the document definition. - -We'll use `ZodOpenApiOperationObject` from `zod-openapi` to add `create burger`, and `read burger` paths: +### 12. Define Burger and Order Operations +Now, define the operations for creating and getting burgers and orders, and listing burgers: ```typescript index.ts -import * as yaml from "yaml"; - -import { z as z3 } from "zod"; -import { z as z4 } from "zod/v4"; - -import { - extendZodWithOpenApi, - ZodOpenApiOperationObject, - createDocument -} from "zod-openapi"; -extendZodWithOpenApi(z3); - -const BurgerIdSchema = z3 - .number() - .min(1) - .openapi({ - ref: "BurgerId", - description: "The unique identifier of the burger.", - example: 1, - param: { - in: "path", - name: "id", - }, - }); - -const burgerSchema = z3.object({ - id: BurgerIdSchema, - name: z3.string().min(1).max(50).openapi({ - description: "The name of the burger.", - example: "Veggie Burger", - }), - description: z3.string().max(255).optional().openapi({ - description: "The description of the burger.", - example: "A delicious bean burger with avocado.", - }), -}); - -burgerSchema.openapi({ - ref: "Burger", - description: "A burger served at the restaurant.", -}); - -const burgerCreateSchema = burgerSchema.omit({ id: true }).openapi({ - ref: "BurgerCreate", - description: "A burger to create.", -}); +import { ZodOpenApiOperationObject } from "zod-openapi"; const createBurger: ZodOpenApiOperationObject = { operationId: "createBurger", summary: "Create a new burger", description: "Creates a new burger in the database.", + tags: ["burgers"], requestBody: { description: "The burger to create.", content: { @@ -730,6 +517,7 @@ const getBurger: ZodOpenApiOperationObject = { operationId: "getBurger", summary: "Get a burger", description: "Gets a burger from the database.", + tags: ["burgers"], requestParams: { path: z3.object({ id: BurgerIdSchema }), }, @@ -745,88 +533,79 @@ const getBurger: ZodOpenApiOperationObject = { }, }; -const document = createDocument({ - openapi: "3.1.0", - info: { - title: "Burger Restaurant API", - description: "An API for managing burgers at a restaurant.", - version: "1.0.0", +const listBurgers: ZodOpenApiOperationObject = { + operationId: "listBurgers", + summary: "List burgers", + description: "Lists all burgers in the database.", + tags: ["burgers"], + responses: { + "200": { + description: "The burgers were retrieved successfully.", + content: { + "application/json": { + schema: z3.array(burgerSchema), + }, + }, + }, }, - servers: [ - { - url: "https://example.com", - description: "The production server.", +}; + +const createOrder: ZodOpenApiOperationObject = { + operationId: "createOrder", + summary: "Create a new order", + description: "Creates a new order in the database.", + tags: ["orders"], + requestBody: { + description: "The order to create.", + content: { + "application/json": { + schema: orderCreateSchema, + }, }, - ], - components: { - schemas: { - burgerSchema, + }, + responses: { + "201": { + description: "The order was created successfully.", + content: { + "application/json": { + schema: orderSchema, + }, + }, }, }, -}); +}; -console.log(yaml.stringify(document)); +const getOrder: ZodOpenApiOperationObject = { + operationId: "getOrder", + summary: "Get an order", + description: "Gets an order from the database.", + tags: ["orders"], + requestParams: { + path: z3.object({ id: OrderIdSchema }), + }, + responses: { + "200": { + description: "The order was retrieved successfully.", + content: { + "application/json": { + schema: orderSchema, + }, + }, + }, + }, +}; ``` -### 14. Add a Webhook That Runs When a Burger Is Created +### 13. Add a Webhook That Runs When a Burger Is Created We'll add a webhook that runs when a burger is created. We'll use the `ZodOpenApiOperationObject` type to define the webhook. ```typescript index.ts -import * as yaml from "yaml"; - -import { z as z3 } from "zod"; -import { z as z4 } from "zod/v4"; - -import { - extendZodWithOpenApi, - ZodOpenApiOperationObject, - createDocument -} from "zod-openapi"; -// Extend the Zod v3 compatible instance for zod-openapi -extendZodWithOpenApi(z3); - -const BurgerIdSchema = z3 - .number() - .min(1) - .openapi({ - ref: "BurgerId", - description: "The unique identifier of the burger.", - example: 1, - param: { - in: "path", - name: "id", - }, - }); - -const burgerSchema = z3.object({ - id: BurgerIdSchema, - name: z3.string().min(1).max(50).openapi({ - description: "The name of the burger.", - example: "Veggie Burger", - }), - description: z3.string().max(255).optional().openapi({ - description: "The description of the burger.", - example: "A delicious bean burger with avocado.", - }), -}); - -burgerSchema.openapi({ - ref: "Burger", - description: "A burger served at the restaurant.", -}); - -const burgerCreateSchema = burgerSchema.omit({ id: true }).openapi({ - ref: "BurgerCreate", - description: "A burger to create.", -}); - -// Create and Get burger operations defined here... - const createBurgerWebhook: ZodOpenApiOperationObject = { operationId: "createBurgerWebhook", summary: "New burger webhook", description: "A webhook that is called when a new burger is created.", + tags: ["burgers"], requestBody: { description: "The burger that was created.", content: { @@ -841,12 +620,18 @@ const createBurgerWebhook: ZodOpenApiOperationObject = { }, }, }; +``` + +### 14. Register All Paths, Webhooks, and Extensions + +Now, register all schemas, paths, webhooks, and the `x-speakeasy-retries` extension: +```typescript index.ts const document = createDocument({ openapi: "3.1.0", info: { title: "Burger Restaurant API", - description: "An API for managing burgers at a restaurant.", + description: "An API for managing burgers and orders at a restaurant.", version: "1.0.0", }, servers: [ @@ -855,65 +640,45 @@ const document = createDocument({ description: "The production server.", }, ], - components: { - schemas: { - burgerSchema, + "x-speakeasy-retries": { + strategy: "backoff", + backoff: { + initialInterval: 500, + maxInterval: 60000, + maxElapsedTime: 3600000, + exponent: 1.5, }, - }, -}); - -console.log(yaml.stringify(document)); -``` - -### 15. Register Paths and Webhooks - -We'll register our paths and webhooks by adding them to the document definition. - -```typescript index.ts -import * as yaml from "yaml"; - -import { z as z3 } from "zod"; -import { z as z4 } from "zod/v4"; - -import { - extendZodWithOpenApi, - ZodOpenApiOperationObject, - createDocument -} from "zod-openapi"; -extendZodWithOpenApi(z3); - -// Previous code from examples above... -// BurgerIdSchema, burgerSchema, burgerCreateSchema, createBurger, getBurger, createBurgerWebhook - -const document = createDocument({ - openapi: "3.1.0", - info: { - title: "Burger Restaurant API", - description: "An API for managing burgers at a restaurant.", - version: "1.0.0", + statusCodes: ["5XX"], + retryConnectionErrors: true, }, paths: { "/burgers": { post: createBurger, + get: listBurgers, }, "/burgers/{id}": { get: getBurger, }, + "/orders": { + post: createOrder, + }, + "/orders/{id}": { + get: getOrder, + }, }, webhooks: { "/burgers": { post: createBurgerWebhook, }, }, - servers: [ - { - url: "https://example.com", - description: "The production server.", - }, - ], components: { schemas: { burgerSchema, + BurgerCreate, + BurgerId, + Order, + OrderCreate, + OrderId, }, }, }); @@ -921,7 +686,7 @@ const document = createDocument({ console.log(yaml.stringify(document)); ``` -### 16. Generate the OpenAPI Schema +### 15. Generate the OpenAPI Schema Run the `index.ts` file to generate the OpenAPI schema. @@ -1194,7 +959,7 @@ components: example: 1 ``` -### 17. Generate an SDK +### 16. Generate an SDK With our OpenAPI schema complete, we can now generate an SDK using the Speakeasy SDK generator. From 00f54c39e99dd8d6dba38c50508a8d7689e6cc4c Mon Sep 17 00:00:00 2001 From: tatenda Date: Thu, 29 May 2025 11:18:45 +0200 Subject: [PATCH 07/11] updates --- openapi/frameworks/zod.mdx | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index 81c2d504..879d379a 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -385,11 +385,6 @@ const document = createDocument({ components: { schemas: { burgerSchema, - BurgerCreate, - BurgerId, - Order, - OrderCreate, - OrderId, }, }, }); @@ -416,6 +411,22 @@ const BurgerIdSchema = z3 }); ``` +Update the `burgerSchema` to use the `BurgerIdSchema`. + +```typescript index.ts +const burgerSchema = z3.object({ + id: BurgerIdSchema, + name: z3.string().min(1).max(50).openapi({ + description: "The name of the burger.", + example: "Veggie Burger", + }), + description: z3.string().max(255).optional().openapi({ + description: "The description of the burger.", + example: "A delicious bean burger with avocado.", + }), +}); +``` + ### 10. Add a Schema for Creating Burgers We'll add a schema for creating burgers that doesn't include an ID. We'll use this schema to define the request body for the create burger path. @@ -427,7 +438,7 @@ const burgerCreateSchema = burgerSchema.omit({ id: true }).openapi({ }); ``` -### 11. Add Order Schemas and Operations +### 11. Add Order Schemas To match the final OpenAPI output, let's add schemas and endpoints for orders. @@ -464,7 +475,7 @@ const orderSchema = z3.object({ description: "The status of the order.", example: "pending", }), - note: z3.string().openapi({ + note: z3.string().optional().openapi({ description: "A note for the order.", example: "No onions.", }), @@ -672,11 +683,11 @@ const document = createDocument({ components: { schemas: { burgerSchema, - BurgerCreate, - BurgerId, - Order, - OrderCreate, - OrderId, + burgerCreateSchema, + BurgerIdSchema, + orderSchema, + orderCreateSchema, + OrderIdSchema, }, }, }); From fd5dc184d7285dc6362f3cdd7d7eba687ea5e853 Mon Sep 17 00:00:00 2001 From: bethh0rn Date: Fri, 6 Jun 2025 15:48:58 +0200 Subject: [PATCH 08/11] Edit: How to generate an OpenAPI document with Zod --- openapi/frameworks/zod.mdx | 117 ++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 60 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index 879d379a..8e4e1a56 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -1,32 +1,31 @@ --- -title: How To Generate an OpenAPI Spec with Zod -description: "How to generate OpenAPI schemas and great SDK clients for your Zod-validated API" +title: How To Generate an OpenAPI Document With Zod +description: "How to generate OpenAPI documents and great SDK clients for your Zod-validated API." --- import { Callout } from "@/mdx/components"; - -# How to generate an OpenAPI/Swagger spec with Zod +# How to generate an OpenAPI document with Zod Zod is a powerful and flexible schema validation library for TypeScript. Many users define their TypeScript data parsing schemes using it. -In this tutorial, we'll take a detailed look at how to set up Zod OpenAPI to generate an OpenAPI schema based on Zod schemas. Then we'll use Speakeasy to read our generated OpenAPI schema and generate a production-ready client SDK. +In this tutorial, we'll take a detailed look at how to set up Zod OpenAPI to generate an OpenAPI document based on Zod schemas. Then we'll use Speakeasy to read our generated OpenAPI document and generate a production-ready SDK. -## Why Zod + OpenAPI? +## Why use Zod with OpenAPI? -Combining Zod with OpenAPI generation offers the best of both worlds: runtime validation and automatic API documentation. Instead of writing schemas twice—once for runtime validation and again for your OpenAPI spec—you define your data models once in Zod and generate both TypeScript types and OpenAPI documentation from the same source. +Combining Zod with OpenAPI generation offers the best of both worlds: runtime validation and automatic API documentation. Instead of writing schemas twice – once for runtime validation and again for your OpenAPI document – you define your data models once in Zod and generate both TypeScript types and OpenAPI documentation from the same source. -This eliminates the task of keeping hand-written OpenAPI specs in sync with your actual API implementation. When paired with Speakeasy's SDK generation, you get type-safe client libraries that automatically stay up-to-date with your API changes. +This eliminates the task of keeping hand-written OpenAPI documents in sync with your actual API implementation. When paired with Speakeasy's SDK generation, you get type-safe client libraries that automatically stay up to date with your API changes. -This guide uses [zod-openapi](https://github.com/samchungy/zod-openapi), which is actively maintained and offers better TypeScript integration than the older zod-to-openapi library. Currently, zod-openapi requires Zod v3 compatibility, which is why we use a dual import strategy throughout this tutorial. +This guide uses [`zod-openapi`](https://github.com/samchungy/zod-openapi), which is actively maintained and offers better TypeScript integration than the older `zod-to-openapi` library. Currently, `zod-openapi` requires Zod v3 compatibility, which is why we use a dual import strategy throughout this tutorial. -While Zod v4 introduces new features like `z.strictObject()` and `z.email()`, you'll need to use the standard Zod import for OpenAPI schemas and the `/v4` subpath for new features until zod-openapi adds full v4 support. Check the [zod-openapi releases](https://github.com/samchungy/zod-openapi/releases) for the latest compatibility updates. +While Zod v4 introduces new features like `z.strictObject()` and `z.email()`, you'll need to use the standard Zod import for OpenAPI schemas and the `/v4` subpath for new features until `zod-openapi` adds full v4 support. Check the [`zod-openapi` releases](https://github.com/samchungy/zod-openapi/releases) for the latest compatibility updates. -## Zod OpenAPI Overview +## Zod OpenAPI overview [Zod OpenAPI](https://github.com/samchungy/zod-openapi) is a TypeScript library that helps developers define OpenAPI schemas as Zod schemas. The stated goal of the project is to cut down on code duplication, and it does a wonderful job of this. @@ -34,21 +33,21 @@ Zod schemas map to OpenAPI schemas well, and the changes required to extract Ope Zod OpenAPI is maintained by one of the contributors to an earlier library called [Zod to OpenAPI](https://github.com/asteasolutions/zod-to-openapi). If you already use Zod to OpenAPI, the syntax will be familiar and you should be able to use either library. If you'd like to convert your _Zod to OpenAPI_ code to _Zod OpenAPI_ code, the _Zod OpenAPI_ library provides helpful [documentation for migrating code](https://github.com/samchungy/zod-openapi#migration). -### Key Concepts +### Key concepts -#### Schemas & z.strictObject +#### Schemas and z.strictObject This guide demonstrates a **dual import strategy** for using both Zod v3 and v4 in the same project. This approach is necessary because `zod-openapi` currently requires Zod v3 compatibility, while you may want to use Zod v4 features elsewhere in your application. -Unlike most libraries, Zod is directly embedded in hundreds of other libraries' public APIs. A normal `zod@4.0.0` release would force every one of those libraries to publish breaking changes simultaneously—a massive "version avalanche." +Unlike most libraries, Zod is directly embedded in hundreds of other libraries' public APIs. A normal `zod@4.0.0` release would force every one of those libraries to publish breaking changes simultaneously – a massive "version avalanche". The subpath approach (inspired by Go modules) lets libraries support both versions with one dependency, providing a gradual migration path for the entire ecosystem. See [Colin's detailed explanation](https://github.com/colinhacks/zod/issues/4371) for the full technical reasoning. -Zod 4 introduces top-level helpers like `z.strictObject()` for objects that reject unknown keys and `z.email()` for email validation. +Zod v4 introduces top-level helpers like `z.strictObject()` for objects that reject unknown keys and `z.email()` for email validation. ```typescript concept.ts import { z as z3 } from "zod"; @@ -63,7 +62,7 @@ const user = z4.strictObject({ #### Field metadata -The `.openapi()` method adds OpenAPI-specific metadata like descriptions and examples to any Zod schema. Use z3 for OpenAPI schemas. +The `.openapi()` method adds OpenAPI-specific metadata like descriptions and examples to any Zod schema. Use `z3` for OpenAPI schemas. ```typescript concept.ts import { z as z3 } from "zod"; @@ -78,7 +77,7 @@ const name = z3.string().min(1).openapi({ #### Operation objects -Use `ZodOpenApiOperationObject` to define API endpoints with request/response schemas. +Use `ZodOpenApiOperationObject` to define API endpoints with request and response schemas. ```typescript concept.ts import { z as z3 } from "zod"; @@ -97,7 +96,7 @@ const getUser: ZodOpenApiOperationObject = { }; ``` -#### Adding Tags to Operations +#### Adding tags to operations Tags help organize your API operations in the generated documentation and SDKs. You can add a `tags` array to each operation object: @@ -112,7 +111,7 @@ const getBurger: ZodOpenApiOperationObject = { #### Speakeasy extensions -Add SDK-specific behavior using `x-speakeasy-*` extensions in your OpenAPI spec. +Add SDK-specific behavior using `x-speakeasy-*` extensions in your OpenAPI document. ```typescript concept.ts const config = { @@ -124,9 +123,9 @@ const config = { }; ``` -#### Using Zod v4 features alongside OpenAPI schemas +#### Using Zod v4 features alongside OpenAPI documents -While your OpenAPI schemas must use the z3 instance for compatibility, you can use Zod v4 features for internal validation, type checking, or other parts of your application: +While your OpenAPI documents must use the `z3` instance for compatibility, you can use `z4` features for internal validation, type checking, or other parts of your application: ```typescript concept.ts // OpenAPI-compatible schemas (use z3) @@ -148,9 +147,9 @@ const internalUserSchema = z4.strictObject({ // v4 feature }); ``` -## Step-by-Step Tutorial: From Zod to OpenAPI to an SDK +## Step-by-step tutorial: From Zod to OpenAPI to an SDK -Now let's walk through the process of generating an OpenAPI schema and SDK for our Burgers and Orders API. +Now let's walk through the process of generating an OpenAPI document and SDK for our Burgers and Orders API. ### Requirements @@ -161,7 +160,7 @@ The following should be installed on your machine: - [Node.js version 18 or above](https://nodejs.org/en/download) - The [Speakeasy CLI](/docs/introduction#install-the-speakeasy-cli), which we'll use to generate an SDK from the OpenAPI document -### 1. Create Your Zod Project +### Creating your Zod project The source code for our complete example is available in the [`speakeasy-api/examples`](https://github.com/speakeasy-api/examples.git) repository in the `zod-openapi` directory. The project contains a pre-generated Python SDK with instructions on how to generate more SDKs. You can clone this repository to test how changes to the Zod schema definition result in changes to the generated SDK. @@ -188,7 +187,7 @@ Next, install the dependencies: npm install ``` -### 2. Install TypeScript Development Tools +### Installing TypeScript development tools For this tutorial, we'll use `tsx` for running TypeScript directly: @@ -196,7 +195,7 @@ For this tutorial, we'll use `tsx` for running TypeScript directly: npm install -D tsx ``` -### 3. Create Your App's First Zod Schema +### Creating your app's first Zod schema Save this TypeScript code in a new file called `index.ts`. Note the dual import strategy: @@ -214,9 +213,9 @@ const burgerSchema = z3.object({ }); ``` -### 4. Extend Zod With OpenAPI +### Extending Zod with OpenAPI -We'll add the `openapi` method to Zod by calling `extendZodWithOpenApi` once. Update `index.ts` to import `extendZodWithOpenApi` from `zod-openapi`, then call `extendZodWithOpenApi` on the z3 instance. +We'll add the `openapi` method to Zod by calling `extendZodWithOpenApi` once. Update `index.ts` to import `extendZodWithOpenApi` from `zod-openapi`, then call `extendZodWithOpenApi` on the `z3` instance. ```typescript index.ts import { z as z3 } from "zod"; @@ -234,7 +233,7 @@ const burgerSchema = z3.object({ }); ``` -### 5. Register and Generate a Component Schema +### Registering and generating a component schema Next, we'll use the new `openapi` method provided by `extendZodWithOpenApi` to register an OpenAPI schema for the `burgerSchema`. Edit `index.ts` and add `.openapi({ref: "Burger"}` to the `burgerSchema` schema object. @@ -256,9 +255,9 @@ const burgerSchema = z3.object({ burgerSchema.openapi({ ref: "Burger" }); ``` -### 6. Add Metadata to Components +### Adding metadata to components -To generate an SDK that offers great developer experience, we recommend adding descriptions and examples to all fields in OpenAPI components. +To generate an SDK that offers a great developer experience, we recommend adding descriptions and examples to all fields in OpenAPI components. With Zod OpenAPI, we'll call the `.openapi` method on each field, and add an example and description to each field. @@ -294,7 +293,7 @@ burgerSchema.openapi({ }); ``` -### 7. Prepare to Generate an OpenAPI Document +### Preparing to generate an OpenAPI document Now that we know how to register components with metadata for our OpenAPI schema, let's generate a complete schema document. @@ -333,7 +332,7 @@ burgerSchema.openapi({ }); ``` -### 8. Generate an OpenAPI Document +### Generating an OpenAPI document We'll use the `createDocument` method to generate an OpenAPI document. We'll pass in the `burgerSchema` and a title for the document. @@ -392,7 +391,7 @@ const document = createDocument({ console.log(yaml.stringify(document)); ``` -### 9. Add a Burger ID Schema +### Adding a burger ID schema To make the burger ID available to other schemas, we'll define a burger ID schema. We'll also use this schema to define a path parameter for the burger ID later on. @@ -427,7 +426,7 @@ const burgerSchema = z3.object({ }); ``` -### 10. Add a Schema for Creating Burgers +### Adding a schema for creating burgers We'll add a schema for creating burgers that doesn't include an ID. We'll use this schema to define the request body for the create burger path. @@ -438,7 +437,7 @@ const burgerCreateSchema = burgerSchema.omit({ id: true }).openapi({ }); ``` -### 11. Add Order Schemas +### Adding order schemas To match the final OpenAPI output, let's add schemas and endpoints for orders. @@ -490,7 +489,7 @@ const orderCreateSchema = orderSchema.omit({ id: true }).openapi({ }); ``` -### 12. Define Burger and Order Operations +### Defining burger and order operations Now, define the operations for creating and getting burgers and orders, and listing burgers: @@ -605,7 +604,7 @@ const getOrder: ZodOpenApiOperationObject = { }; ``` -### 13. Add a Webhook That Runs When a Burger Is Created +### Adding a webhook that runs when a burger is created We'll add a webhook that runs when a burger is created. We'll use the `ZodOpenApiOperationObject` type to define the webhook. @@ -631,7 +630,7 @@ const createBurgerWebhook: ZodOpenApiOperationObject = { }; ``` -### 14. Register All Paths, Webhooks, and Extensions +### Registering all paths, webhooks, and extensions Now, register all schemas, paths, webhooks, and the `x-speakeasy-retries` extension: @@ -695,9 +694,9 @@ const document = createDocument({ console.log(yaml.stringify(document)); ``` -### 15. Generate the OpenAPI Schema +### Generating the OpenAPI document -Run the `index.ts` file to generate the OpenAPI schema. +Run the `index.ts` file to generate the OpenAPI document. ```bash Terminal npx tsx index.ts > openapi.yaml @@ -968,11 +967,11 @@ components: example: 1 ``` -### 16. Generate an SDK +### Generating an SDK -With our OpenAPI schema complete, we can now generate an SDK using the Speakeasy SDK generator. +With our OpenAPI document complete, we can now generate an SDK using the Speakeasy SDK generator. -#### Install the Speakeasy CLI +#### Installing the Speakeasy CLI First, install the Speakeasy CLI: @@ -984,27 +983,27 @@ brew install speakeasy-api/tap/speakeasy curl -fsSL https://go.speakeasy.com/cli-install.sh | sh ``` -#### Lint Your OpenAPI Spec +#### Linting your OpenAPI document -Before generating SDKs, lint your spec to catch common issues: +Before generating SDKs, lint your OpenAPI document to catch common issues: ```bash Terminal speakeasy lint openapi --schema openapi.yaml ``` -#### Use AI to Improve Your Spec +#### Using AI to improve your OpenAPI document -The Speakeasy CLI now includes AI-powered suggestions to automatically improve your OpenAPI specifications: +The Speakeasy CLI now includes AI-powered suggestions to automatically improve your OpenAPI documents: ```bash Terminal speakeasy suggest openapi.yaml ``` -Follow the onscreen prompts to provide the necessary configuration details for your new SDK such as the schema and output path. +Follow the onscreen prompts to provide the necessary configuration details for your new SDK, such as the schema and output path. Read the [Speakeasy Suggest](/docs/prep-openapi/maintenance) documentation for more information on how to use Speakeasy Suggest. -#### Generate Your SDK +#### Generating your SDK Now you can generate your SDK using the quickstart command: @@ -1012,26 +1011,25 @@ Now you can generate your SDK using the quickstart command: speakeasy quickstart ``` -Follow the onscreen prompts to provide the necessary configuration details for your new SDK such as the name, schema location and output path. Enter `openapi.yaml` (or your improved spec if you used suggestions) when prompted for the OpenAPI document location and select your preferred language when prompted. +Follow the onscreen prompts to provide the necessary configuration details for your new SDK, such as the name, schema location, and output path. Enter `openapi.yaml` (or your improved OpenAPI document if you used suggestions) when prompted for the OpenAPI document location, and select your preferred language when prompted. -## Using Your Generated SDK +## Using your generated SDK Once you've generated your SDK, you can [publish](/docs/publish-sdk) it for use. For TypeScript, you can publish it as an npm package. -TypeScript SDKs generated with Speakeasy include an installable [Model Context Protocol (MCP) server](/docs/model-context-protocol) where the various SDK methods are exposed as tools that can be invoked by AI applications. Your SDK documentation includes instructions for installing the MCP server. +TypeScript SDKs generated with Speakeasy include an installable [Model Context Protocol (MCP) server](/docs/model-context-protocol) where the various SDK methods are exposed as tools that AI applications can invoke. Your SDK documentation includes instructions for installing the MCP server. Note that the SDK is not ready for production use immediately after generation. To get it production-ready, follow the steps outlined in your Speakeasy workspace. -### Adding SDK Generation to Your CI/CD Pipeline +### Adding SDK generation to your CI/CD pipeline The Speakeasy [`sdk-generation-action`](https://github.com/speakeasy-api/sdk-generation-action) repository provides workflows for integrating the Speakeasy CLI into CI/CD pipelines to automatically regenerate SDKs when your Zod schemas change. You can set up Speakeasy to automatically push a new branch to your SDK repositories so that your engineers can review and merge the SDK changes. -For an overview of how to set up automation for your SDKs, see the Speakeasy [SDK Workflow syntax reference](/docs/speakeasy-reference/workflow-file). - +For an overview of how to set up automation for your SDKs, see the Speakeasy [SDK workflow syntax reference](/docs/speakeasy-reference/workflow-file). ## Summary @@ -1039,10 +1037,9 @@ In this tutorial, we learned how to generate OpenAPI schemas from Zod and create By following these steps, you can ensure that your API is well-documented, easy to use, and offers a great developer experience. -### Further Reading +### Further reading This guide covered the basics of generating an OpenAPI document using Zod OpenAPI. Here are some resources to help you learn more about Zod, OpenAPI, and Speakeasy: -- [Zod OpenAPI documentation](https://github.com/samchungy/zod-openapi): Learn more about the zod-openapi library, including advanced features like custom serializers and middleware integration. -- [Zod documentation](https://zod.dev/): Comprehensive guide to Zod schema validation, including the latest v4 features. - +- [The Zod OpenAPI documentation](https://github.com/samchungy/zod-openapi): Learn more about the `zod-openapi` library, including advanced features like custom serializers and middleware integration. +- [The Zod documentation](https://zod.dev/): Comprehensive guide to Zod schema validation, including the latest v4 features. From 83b7dbab01f33d0e8e735dc08e47c44c989e3261 Mon Sep 17 00:00:00 2001 From: Abdul Baari Davids Date: Sun, 29 Jun 2025 22:55:12 +0200 Subject: [PATCH 09/11] feedback: zod openapi --- openapi/frameworks/zod.mdx | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index 8e4e1a56..2b4e0b2d 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -7,9 +7,16 @@ import { Callout } from "@/mdx/components"; # How to generate an OpenAPI document with Zod -Zod is a powerful and flexible schema validation library for TypeScript. Many users define their TypeScript data parsing schemes using it. +Zod is a powerful and flexible schema validation library for TypeScript. Many +users define their TypeScript data parsing schemes using it. -In this tutorial, we'll take a detailed look at how to set up Zod OpenAPI to generate an OpenAPI document based on Zod schemas. Then we'll use Speakeasy to read our generated OpenAPI document and generate a production-ready SDK. +This tutorial shows you how to generate OpenAPI documents from TypeScript schemas and create production-ready SDKs. Here's what we'll work with: + +- **Zod** - A TypeScript schema validation library +- **zod-openapi** - The npm package that converts Zod schemas to OpenAPI format +- **Speakeasy** - The tool that generates SDKs from your OpenAPI document + +In this tutorial, we'll use the `zod-openapi` package to convert Zod schemas into a complete OpenAPI document, then use Speakeasy to generate a production-ready SDK from that document. ## Why use Zod with OpenAPI? @@ -27,26 +34,29 @@ While Zod v4 introduces new features like `z.strictObject()` and `z.email()`, yo ## Zod OpenAPI overview -[Zod OpenAPI](https://github.com/samchungy/zod-openapi) is a TypeScript library that helps developers define OpenAPI schemas as Zod schemas. The stated goal of the project is to cut down on code duplication, and it does a wonderful job of this. +The [`zod-openapi`](https://github.com/samchungy/zod-openapi) package is a TypeScript library that helps developers define OpenAPI schemas as Zod schemas. The stated goal of the project is to cut down on code duplication, and it does a wonderful job of this. Zod schemas map to OpenAPI schemas well, and the changes required to extract OpenAPI documents from a schema defined in Zod are often small. -Zod OpenAPI is maintained by one of the contributors to an earlier library called [Zod to OpenAPI](https://github.com/asteasolutions/zod-to-openapi). If you already use Zod to OpenAPI, the syntax will be familiar and you should be able to use either library. If you'd like to convert your _Zod to OpenAPI_ code to _Zod OpenAPI_ code, the _Zod OpenAPI_ library provides helpful [documentation for migrating code](https://github.com/samchungy/zod-openapi#migration). + +If you're currently using the older `zod-to-openapi` library, the syntax will be familiar and you can use either library. For migration guidance, see the [zod-openapi migration documentation](https://github.com/samchungy/zod-openapi#migration). + ### Key concepts -#### Schemas and z.strictObject This guide demonstrates a **dual import strategy** for using both Zod v3 and v4 in the same project. This approach is necessary because `zod-openapi` currently requires Zod v3 compatibility, while you may want to use Zod v4 features elsewhere in your application. -Unlike most libraries, Zod is directly embedded in hundreds of other libraries' public APIs. A normal `zod@4.0.0` release would force every one of those libraries to publish breaking changes simultaneously – a massive "version avalanche". +Unlike most libraries, Zod is directly embedded in hundreds of other libraries' public APIs. A normal `zod@4.0.0` release would force every one of those libraries to publish breaking changes simultaneously - a massive "version avalanche". The subpath approach (inspired by Go modules) lets libraries support both versions with one dependency, providing a gradual migration path for the entire ecosystem. See [Colin's detailed explanation](https://github.com/colinhacks/zod/issues/4371) for the full technical reasoning. +#### Schemas and z.strictObject + Zod v4 introduces top-level helpers like `z.strictObject()` for objects that reject unknown keys and `z.email()` for email validation. ```typescript concept.ts @@ -109,19 +119,6 @@ const getBurger: ZodOpenApiOperationObject = { }; ``` -#### Speakeasy extensions - -Add SDK-specific behavior using `x-speakeasy-*` extensions in your OpenAPI document. - -```typescript concept.ts -const config = { - info: { title: "My API", version: "1.0.0" }, - "x-speakeasy-retries": { - strategy: "backoff", - backoff: { initialInterval: 500, maxInterval: 60000 } - } -}; -``` #### Using Zod v4 features alongside OpenAPI documents @@ -694,6 +691,8 @@ const document = createDocument({ console.log(yaml.stringify(document)); ``` +Speakeasy will read the `x-speakeasy-*` extensions to configure the SDK. In this example, the `x-speakeasy-retries` extension will configure the SDK to retry failed requests. For more information on the available extensions, see the [extensions guide](../extensions.mdx). + ### Generating the OpenAPI document Run the `index.ts` file to generate the OpenAPI document. From 9dbbfbbcf5af2c2807300a25f2b3d5c9dc369767 Mon Sep 17 00:00:00 2001 From: bethh0rn Date: Mon, 30 Jun 2025 15:04:54 +0200 Subject: [PATCH 10/11] Edit: zod.mdx feedback --- openapi/frameworks/zod.mdx | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index 2b4e0b2d..c03db314 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -7,16 +7,10 @@ import { Callout } from "@/mdx/components"; # How to generate an OpenAPI document with Zod -Zod is a powerful and flexible schema validation library for TypeScript. Many -users define their TypeScript data parsing schemes using it. +Zod is a powerful and flexible schema validation library for TypeScript, which many developers use to define their TypeScript data parsing schemas. -This tutorial shows you how to generate OpenAPI documents from TypeScript schemas and create production-ready SDKs. Here's what we'll work with: +This tutorial demonstrates how to use another TypeScript library, the `zod-openapi` npm package, to convert Zod schemas into a complete OpenAPI document, and then how to use Speakeasy to generate a production-ready SDK from that document. -- **Zod** - A TypeScript schema validation library -- **zod-openapi** - The npm package that converts Zod schemas to OpenAPI format -- **Speakeasy** - The tool that generates SDKs from your OpenAPI document - -In this tutorial, we'll use the `zod-openapi` package to convert Zod schemas into a complete OpenAPI document, then use Speakeasy to generate a production-ready SDK from that document. ## Why use Zod with OpenAPI? @@ -36,17 +30,17 @@ While Zod v4 introduces new features like `z.strictObject()` and `z.email()`, yo The [`zod-openapi`](https://github.com/samchungy/zod-openapi) package is a TypeScript library that helps developers define OpenAPI schemas as Zod schemas. The stated goal of the project is to cut down on code duplication, and it does a wonderful job of this. -Zod schemas map to OpenAPI schemas well, and the changes required to extract OpenAPI documents from a schema defined in Zod are often small. +Zod schemas map well to OpenAPI schemas, and the changes required to extract OpenAPI documents from a schema defined in Zod are often small. -If you're currently using the older `zod-to-openapi` library, the syntax will be familiar and you can use either library. For migration guidance, see the [zod-openapi migration documentation](https://github.com/samchungy/zod-openapi#migration). +If you're currently using the older `zod-to-openapi` library, the syntax will be familiar, and you can use either library. For migration guidance, see the [`zod-openapi` migration documentation](https://github.com/samchungy/zod-openapi#migration). ### Key concepts -This guide demonstrates a **dual import strategy** for using both Zod v3 and v4 in the same project. This approach is necessary because `zod-openapi` currently requires Zod v3 compatibility, while you may want to use Zod v4 features elsewhere in your application. +This guide demonstrates a **dual import strategy** for using both Zod v3 and v4 in the same project. This approach is necessary because `zod-openapi` currently requires Zod v3 compatibility, and you may want to use Zod v4 features elsewhere in your application. Unlike most libraries, Zod is directly embedded in hundreds of other libraries' public APIs. A normal `zod@4.0.0` release would force every one of those libraries to publish breaking changes simultaneously - a massive "version avalanche". From 371296e397fd7b817f6c5bf3cd78a85176a71b0a Mon Sep 17 00:00:00 2001 From: bethh0rn Date: Mon, 30 Jun 2025 15:08:44 +0200 Subject: [PATCH 11/11] Make Zod OpenAPI formatting consistent --- openapi/frameworks/zod.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openapi/frameworks/zod.mdx b/openapi/frameworks/zod.mdx index c03db314..86fb2f15 100644 --- a/openapi/frameworks/zod.mdx +++ b/openapi/frameworks/zod.mdx @@ -26,7 +26,7 @@ While Zod v4 introduces new features like `z.strictObject()` and `z.email()`, yo -## Zod OpenAPI overview +## zod-openapi overview The [`zod-openapi`](https://github.com/samchungy/zod-openapi) package is a TypeScript library that helps developers define OpenAPI schemas as Zod schemas. The stated goal of the project is to cut down on code duplication, and it does a wonderful job of this. @@ -250,7 +250,7 @@ burgerSchema.openapi({ ref: "Burger" }); To generate an SDK that offers a great developer experience, we recommend adding descriptions and examples to all fields in OpenAPI components. -With Zod OpenAPI, we'll call the `.openapi` method on each field, and add an example and description to each field. +With `zod-openapi`, we'll call the `.openapi` method on each field, and add an example and description to each field. We'll also add a description to the `Burger` component itself. @@ -1032,7 +1032,7 @@ By following these steps, you can ensure that your API is well-documented, easy ### Further reading -This guide covered the basics of generating an OpenAPI document using Zod OpenAPI. Here are some resources to help you learn more about Zod, OpenAPI, and Speakeasy: +This guide covered the basics of generating an OpenAPI document using `zod-openapi`. Here are some resources to help you learn more about Zod, OpenAPI, and Speakeasy: -- [The Zod OpenAPI documentation](https://github.com/samchungy/zod-openapi): Learn more about the `zod-openapi` library, including advanced features like custom serializers and middleware integration. +- [The `zod-openapi` documentation](https://github.com/samchungy/zod-openapi): Learn more about the `zod-openapi` library, including advanced features like custom serializers and middleware integration. - [The Zod documentation](https://zod.dev/): Comprehensive guide to Zod schema validation, including the latest v4 features.