Generate SDKs With Zod

Many Speakeasy users define their TypeScript data parsing schemas using Zod, a powerful and flexible schema validation library for TypeScript.

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

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.

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.

An Overview of Zod OpenAPI

Zod OpenAPI (opens in a new tab) 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 (opens in a new tab). 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 (opens in a new tab).

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.

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.

Then, initialize a new npm project and install Zod:

Terminal

mkdir zod-burgers
cd zod-burgers
npm init -y
npm install zod

2. Install the Zod OpenAPI Library

Use npm to install zod-openapi:

Terminal

npm install zod-openapi

3. Create Your App's First Zod Schema

Save this TypeScript code in a new file called index.ts.

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(),
});

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.

index.ts

import { z } from "zod";
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(),
});

5. Register and Generate 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.

We'll also add an OpenAPI generator, OpenApiGeneratorV31, and log the generated component to the console as YAML.

index.ts

import { z } from "zod";
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(),
});
burgerSchema.openapi({ ref: "Burger" });

6. Add Metadata to Components

To generate an SDK that offers 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.

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.

index.ts

import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
const burgerSchema = z.object({
id: z.number().min(1).openapi({
description: "The unique identifier of the burger.",
example: 1,
}),
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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.",
});

7. Prepare 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.

Import yaml and createDocument.

index.ts

import * as yaml from "yaml";
import { z } from "zod";
import {
extendZodWithOpenApi,
createDocument
} from "zod-openapi";
extendZodWithOpenApi(z);
const burgerSchema = z.object({
id: z.number().min(1).openapi({
description: "The unique identifier of the burger.",
example: 1,
}),
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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.",
});

8. Generate 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.

index.ts

import * as yaml from "yaml";
import { z } from "zod";
import {
extendZodWithOpenApi,
createDocument
} from "zod-openapi";
extendZodWithOpenApi(z);
const burgerSchema = z.object({
id: z.number().min(1).openapi({
description: "The unique identifier of the burger.",
example: 1,
}),
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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 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));

9. Run the Code

Run the code in the terminal:

index.ts
Output

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:
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.

Terminal

npx ts-node index.ts

10. 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.

index.ts
Output

import * as yaml from "yaml";
import { z } from "zod";
import {
extendZodWithOpenApi,
createDocument
} from "zod-openapi";
extendZodWithOpenApi(z);
const BurgerIdSchema = z
.number()
.min(1)
.openapi({
ref: "BurgerId",
description: "The unique identifier of the burger.",
example: 1,
param: {
in: "path",
name: "id",
},
});
const burgerSchema = z.object({
id: BurgerIdSchema,
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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 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));

11. Replace the Burger ID Field With a Reference

We'll replace the burger ID field with a reference to the burger ID schema.

index.ts
Output

import * as yaml from "yaml";
import { z } from "zod";
import {
extendZodWithOpenApi,
createDocument
} from "zod-openapi";
extendZodWithOpenApi(z);
const BurgerIdSchema = z
.number()
.min(1)
.openapi({
ref: "BurgerId",
description: "The unique identifier of the burger.",
example: 1,
param: {
in: "path",
name: "id",
},
});
const burgerSchema = z.object({
id: BurgerIdSchema,
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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 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.

index.ts
Output

import * as yaml from "yaml";
import { z } from "zod";
import {
extendZodWithOpenApi,
createDocument
} from "zod-openapi";
extendZodWithOpenApi(z);
const BurgerIdSchema = z
.number()
.min(1)
.openapi({
ref: "BurgerId",
description: "The unique identifier of the burger.",
example: 1,
param: {
in: "path",
name: "id",
},
});
const burgerSchema = z.object({
id: BurgerIdSchema,
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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.",
});
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.

Start by importing ZodOpenApiOperationObject from zod-openapi.

index.ts
Output

import * as yaml from "yaml";
import { z } from "zod";
import {
extendZodWithOpenApi,
ZodOpenApiOperationObject,
createDocument
} from "zod-openapi";
extendZodWithOpenApi(z);
const BurgerIdSchema = z
.number()
.min(1)
.openapi({
ref: "BurgerId",
description: "The unique identifier of the burger.",
example: 1,
param: {
in: "path",
name: "id",
},
});
const burgerSchema = z.object({
id: BurgerIdSchema,
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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.",
});
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));

14. Add a Create Burger Path

We'll add a path for creating a new burger. We'll use the ZodOpenApiOperationObject type to define the path.

index.ts
Output

import * as yaml from "yaml";
import { z } from "zod";
import {
extendZodWithOpenApi,
ZodOpenApiOperationObject,
createDocument
} from "zod-openapi";
extendZodWithOpenApi(z);
const BurgerIdSchema = z
.number()
.min(1)
.openapi({
ref: "BurgerId",
description: "The unique identifier of the burger.",
example: 1,
param: {
in: "path",
name: "id",
},
});
const burgerSchema = z.object({
id: BurgerIdSchema,
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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.",
});
const createBurger: ZodOpenApiOperationObject = {
operationId: "createBurger",
summary: "Create a new burger",
description: "Creates a new burger in the database.",
requestBody: {
description: "The burger to create.",
content: {
"application/json": {
schema: burgerCreateSchema,
},
},
},
responses: {
"201": {
description: "The burger was created successfully.",
content: {
"application/json": {
schema: burgerSchema,
},
},
},
},
};
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));

15. Add a Read Burger Path

We'll add a path for fetching a burger by ID. We'll use the ZodOpenApiOperationObject type to define the path.

index.ts
Output

import * as yaml from "yaml";
import { z } from "zod";
import {
extendZodWithOpenApi,
ZodOpenApiOperationObject,
createDocument
} from "zod-openapi";
extendZodWithOpenApi(z);
const BurgerIdSchema = z
.number()
.min(1)
.openapi({
ref: "BurgerId",
description: "The unique identifier of the burger.",
example: 1,
param: {
in: "path",
name: "id",
},
});
const burgerSchema = z.object({
id: BurgerIdSchema,
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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.",
});
const createBurger: ZodOpenApiOperationObject = {
operationId: "createBurger",
summary: "Create a new burger",
description: "Creates a new burger in the database.",
requestBody: {
description: "The burger to create.",
content: {
"application/json": {
schema: burgerCreateSchema,
},
},
},
responses: {
"201": {
description: "The burger was created successfully.",
content: {
"application/json": {
schema: burgerSchema,
},
},
},
},
};
const getBurger: ZodOpenApiOperationObject = {
operationId: "getBurger",
summary: "Get a burger",
description: "Gets a burger from the database.",
requestParams: {
path: z.object({ id: BurgerIdSchema }),
},
responses: {
"200": {
description: "The burger was retrieved successfully.",
content: {
"application/json": {
schema: burgerSchema,
},
},
},
},
};
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));

16. 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.

index.ts
Output

import * as yaml from "yaml";
import { z } from "zod";
import {
extendZodWithOpenApi,
ZodOpenApiOperationObject,
createDocument
} from "zod-openapi";
extendZodWithOpenApi(z);
const BurgerIdSchema = z
.number()
.min(1)
.openapi({
ref: "BurgerId",
description: "The unique identifier of the burger.",
example: 1,
param: {
in: "path",
name: "id",
},
});
const burgerSchema = z.object({
id: BurgerIdSchema,
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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.",
});
const createBurger: ZodOpenApiOperationObject = {
operationId: "createBurger",
summary: "Create a new burger",
description: "Creates a new burger in the database.",
requestBody: {
description: "The burger to create.",
content: {
"application/json": {
schema: burgerCreateSchema,
},
},
},
responses: {
"201": {
description: "The burger was created successfully.",
content: {
"application/json": {
schema: burgerSchema,
},
},
},
},
};
const getBurger: ZodOpenApiOperationObject = {
operationId: "getBurger",
summary: "Get a burger",
description: "Gets a burger from the database.",
requestParams: {
path: z.object({ id: BurgerIdSchema }),
},
responses: {
"200": {
description: "The burger was retrieved successfully.",
content: {
"application/json": {
schema: burgerSchema,
},
},
},
},
};
const createBurgerWebhook: ZodOpenApiOperationObject = {
operationId: "createBurgerWebhook",
summary: "New burger webhook",
description: "A webhook that is called when a new burger is created.",
requestBody: {
description: "The burger that was created.",
content: {
"application/json": {
schema: burgerSchema,
},
},
},
responses: {
"200": {
description: "The webhook was processed successfully.",
},
},
};
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));

17. Register Paths and Webhooks

We'll register our paths and webhooks by adding them to the document definition.

index.ts
Output

import * as yaml from "yaml";
import { z } from "zod";
import {
extendZodWithOpenApi,
ZodOpenApiOperationObject,
createDocument
} from "zod-openapi";
extendZodWithOpenApi(z);
const BurgerIdSchema = z
.number()
.min(1)
.openapi({
ref: "BurgerId",
description: "The unique identifier of the burger.",
example: 1,
param: {
in: "path",
name: "id",
},
});
const burgerSchema = z.object({
id: BurgerIdSchema,
name: z.string().min(1).max(50).openapi({
description: "The name of the burger.",
example: "Veggie Burger",
}),
description: z.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.",
});
const createBurger: ZodOpenApiOperationObject = {
operationId: "createBurger",
summary: "Create a new burger",
description: "Creates a new burger in the database.",
requestBody: {
description: "The burger to create.",
content: {
"application/json": {
schema: burgerCreateSchema,
},
},
},
responses: {
"201": {
description: "The burger was created successfully.",
content: {
"application/json": {
schema: burgerSchema,
},
},
},
},
};
const getBurger: ZodOpenApiOperationObject = {
operationId: "getBurger",
summary: "Get a burger",
description: "Gets a burger from the database.",
requestParams: {
path: z.object({ id: BurgerIdSchema }),
},
responses: {
"200": {
description: "The burger was retrieved successfully.",
content: {
"application/json": {
schema: burgerSchema,
},
},
},
},
};
const createBurgerWebhook: ZodOpenApiOperationObject = {
operationId: "createBurgerWebhook",
summary: "New burger webhook",
description: "A webhook that is called when a new burger is created.",
requestBody: {
description: "The burger that was created.",
content: {
"application/json": {
schema: burgerSchema,
},
},
},
responses: {
"200": {
description: "The webhook was processed successfully.",
},
},
};
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",
},
paths: {
"/burgers": {
post: createBurger,
},
"/burgers/{id}": {
get: getBurger,
},
},
webhooks: {
"/burgers": {
post: createBurgerWebhook,
},
},
servers: [
{
url: "https://example.com",
description: "The production server.",
},
],
components: {
schemas: {
burgerSchema,
},
},
});
console.log(yaml.stringify(document));

18. Run the Code

Run the code in the terminal:

index.ts
Output

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.
paths:
/burgers:
post:
operationId: createBurger
summary: Create a new burger
description: Creates a new burger in the database.
requestBody:
description: The burger to create.
content:
application/json:
schema:
$ref: "#/components/schemas/BurgerCreate"
responses:
"201":
description: The burger was created successfully.
content:
application/json:
schema:
$ref: "#/components/schemas/burgerSchema"
"/burgers/{id}":
get:
operationId: getBurger
summary: Get a burger
description: Gets a burger from the database.
parameters:
- in: path
name: id
schema:
$ref: "#/components/schemas/BurgerId"
required: true
responses:
"200":
description: The burger was retrieved successfully.
content:
application/json:
schema:
$ref: "#/components/schemas/burgerSchema"
webhooks:
/burgers:
post:
operationId: createBurgerWebhook
summary: New burger webhook
description: A webhook that is called when a new burger is created.
requestBody:
description: The burger that was created.
content:
application/json:
schema:
$ref: "#/components/schemas/burgerSchema"
responses:
"200":
description: The webhook was processed successfully.
components:
schemas:
burgerSchema:
type: object
properties:
id:
$ref: "#/components/schemas/BurgerId"
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
BurgerCreate:
type: object
properties:
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:
- name
description: A burger to create.
BurgerId:
type: number
minimum: 1
description: The unique identifier of the burger.
example: 1

Terminal

npx ts-node index.ts

3. Create Your App's First Zod Schema

Save this TypeScript code in a new file called index.ts.

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.

5. Register and Generate 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.

We'll also add an OpenAPI generator, OpenApiGeneratorV31, and log the generated component to the console as YAML.

6. Add Metadata to Components

To generate an SDK that offers 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.

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.

7. Prepare 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.

Import yaml and createDocument.

8. Generate 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.

9. Run the Code

Run the code in the terminal:

Terminal

npx ts-node index.ts

10. 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.

11. Replace the Burger ID Field With a Reference

We'll replace the burger ID field with a reference to the burger ID schema.

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.

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.

Start by importing ZodOpenApiOperationObject from zod-openapi.

14. Add a Create Burger Path

We'll add a path for creating a new burger. We'll use the ZodOpenApiOperationObject type to define the path.

15. Add a Read Burger Path

We'll add a path for fetching a burger by ID. We'll use the ZodOpenApiOperationObject type to define the path.

16. 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.

17. Register Paths and Webhooks

We'll register our paths and webhooks by adding them to the document definition.

18. Run the Code

Run the code in the terminal:

Terminal

npx ts-node index.ts

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(),
});

19. Generate an SDK

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.

First, write your YAML schema to a new file called openapi.yaml. Run the following in the terminal:

Terminal

npx ts-node index.ts > openapi.yaml

Then, log in to your Speakeasy account or use the Speakeasy CLI to generate a new SDK.

Here's how to use the CLI. In the terminal, run:

Terminal

speakeasy generate sdk --schema openapi.yaml --lang python --out ./sdk

This generates a new Python SDK in the ./sdk directory.

Example Zod Schema and SDK Generator

The source code for our complete example is available in the zod-burgers (opens in a new tab) repository.

The repository contains a pregenerated 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.

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.