Generate a zodios (typescript http client with zod validation) from an OpenAPI spec (json/yaml)

Overview

openapi-zod-client

Generates a zodios (typescript http client with zod validation) from a (json/yaml) OpenAPI spec (or just use the generated schemas/endpoints/etc !)

  • can be used programmatically (do w/e you want with the computed schemas/endpoints)

  • or used as a CLI (generates a prettier .ts file with deduplicated variables when pointing to the same schema/$ref)

  • client typesafety using zodios

  • tested (using vitest) against official OpenAPI specs samples

Why this exists

sometimes you don't have control on your API, maybe you need to consume APIs from other teams (who might each use a different language/framework), you only have their Open API spec as source of truth, then this might help 😇

you could use openapi-zod-client to automate the API integration part (doesn't matter if you consume it in your front or back-end, zodios is agnostic) on your CI and just import the generated api client

Comparison vs tRPC etc

please just use tRPC or alternatives if you do have control on your API/back-end

Usage

with local install:

  • pnpm i -D openapi-zod-client
  • pnpm openapi-zod-client "./input/file.json" -o "./output/client.ts"

or directly

  • pnpx openapi-zod-client "./input/file.yaml" -o "./output/client.ts"

CLI

Usage:
  $ openapi-zod-client <input>

Commands:
  <input>  path/url to OpenAPI/Swagger document as json/yaml

For more info, run any command with the `--help` flag:
  $ openapi-zod-client --help

Options:
  -o, --output <path>    Output path for the zodios api client ts file (defaults to `<input>.ts`)
  -t, --template <path>  Template path for the handlebars template that will be used to generate the output
  -p, --prettier <path>  Prettier config path that will be used to format the output client file
  -b, --base-url <url>   Base url for the api
  -a, --with-alias       With alias as api client methods
  -v, --version          Display version number
  -h, --help             Display this message

Customization

You can pass a custom handlebars template and/or a custom prettier config with something like: pnpm openapi-zod-client ./example/petstore.yaml -o ./example/petstore-schemas.ts -t ./example/schemas-only.hbs -p ./example/prettier-custom.json, there is an example here

Tips

  • You can omit the -o (output path) argument if you want and it will default to the input path with a .ts extension: pnpm openapi-zod-client ./input.yaml will generate a ./input.yaml.ts file

  • Since internally we're using swagger-parser, you should be able to use an URL as input like this: pnpx openapi-zod-client https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml -o ./petstore.ts

  • Also, multiple-files-documents ($ref pointing to another file) should work out-of-the-box as well, but if it doesn't, maybe dereferencing your document before passing it to openapi-zod-client could help

Example

tl;dr

input:

openapi: "3.0.0"
info:
    version: 1.0.0
    title: Swagger Petstore
    license:
        name: MIT
servers:
    - url: http://petstore.swagger.io/v1
paths:
    /pets:
        get:
            summary: List all pets
            operationId: listPets
            tags:
                - pets
            parameters:
                - name: limit
                  in: query
                  description: How many items to return at one time (max 100)
                  required: false
                  schema:
                      type: integer
                      format: int32
            responses:
                "200":
                    description: A paged array of pets
                    headers:
                        x-next:
                            description: A link to the next page of responses
                            schema:
                                type: string
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Pets"
                default:
                    description: unexpected error
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
        post:
            summary: Create a pet
            operationId: createPets
            tags:
                - pets
            responses:
                "201":
                    description: Null response
                default:
                    description: unexpected error
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
    /pets/{petId}:
        get:
            summary: Info for a specific pet
            operationId: showPetById
            tags:
                - pets
            parameters:
                - name: petId
                  in: path
                  required: true
                  description: The id of the pet to retrieve
                  schema:
                      type: string
            responses:
                "200":
                    description: Expected response to a valid request
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Pet"
                default:
                    description: unexpected error
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
components:
    schemas:
        Pet:
            type: object
            required:
                - id
                - name
            properties:
                id:
                    type: integer
                    format: int64
                name:
                    type: string
                tag:
                    type: string
        Pets:
            type: array
            items:
                $ref: "#/components/schemas/Pet"
        Error:
            type: object
            required:
                - code
                - message
            properties:
                code:
                    type: integer
                    format: int32
                message:
                    type: string

output:

import { Zodios } from "@zodios/core";
import { z } from "zod";

const v7LgRCMpuZ0 = z.object({ id: z.bigint(), name: z.string(), tag: z.string().optional() }).optional();
const vWZd2G8UeSs = z.array(v7LgRCMpuZ0).optional();
const v77smkx5YEB = z.object({ code: z.bigint(), message: z.string() }).optional();

const variables = {
    Error: v77smkx5YEB,
    Pet: v7LgRCMpuZ0,
    Pets: vWZd2G8UeSs,
    createPets: v77smkx5YEB,
    listPets: v77smkx5YEB,
    showPetById: v77smkx5YEB,
};

const endpoints = [
    {
        method: "get",
        path: "/pets",
        requestFormat: "json",
        parameters: [
            {
                name: "limit",
                type: "Query",
                schema: z.bigint().optional(),
            },
        ],
        response: variables["Pets"],
    },
    {
        method: "post",
        path: "/pets",
        requestFormat: "json",
        response: variables["Error"],
    },
    {
        method: "get",
        path: "/pets/:petId",
        requestFormat: "json",
        response: variables["Pet"],
    },
] as const;

export const api = new Zodios(endpoints);

TODO

  • handle default values (output z.default(xxx))
  • handle OA spec format: date-time -> output z.date() / preprocess ?
  • handle string/number constraints -> output z.min max length email url uuid startsWith endsWith regex trim nonempty gt gte lt lte int positive nonnegative negative nonpositive multipleOf
  • handle OA prefixItems -> output z.tuple
  • handle recursive schemas -> output z.lazy()
  • add an argument to control which response should be added (currently by status code === "200" or when there is a "default")
  • rm unused (=never referenced) variables from output

Caveats

NOT tested/expected to work with OpenAPI before v3, please migrate your specs to v3+ if you want to use this

Contributing:

  • pnpm i && pnpm gen

if you fix an edge case please make a dedicated minimal reproduction test in the tests folder so that it doesn't break in future versions

Comments
  • Numerical enum support (and some others)

    Numerical enum support (and some others)

    Hi,

    In v1.4.5, numerical enums convert to a union of string literals. However, Zod officially says that numerical enums should be a union of numeric literals. (ref: https://github.com/colinhacks/zod/issues/338) And an unnecessary .int() is added if the type is an integer.

    openapi: 3.0.0
    info:
      version: 1.0.0
      title: Numerical enums
    paths:
      /sample:
        get:
          parameters:
            - in: query
              name: foo
              schema:
                type: integer
                enum:
                  - 1
                  - -2
                  - 3
            - in: query
              name: bar
              schema:
                type: number
                enum:
                  - 1.2
                  - 34
                  - -56.789
          responses:
            "200":
              description: resoponse
    
    parameters: [
        {
            name: "foo",
            type: "Query",
            schema: z
                .union([z.literal("1"), z.literal("-2"), z.literal("3")])
                .int() // should remove
                .optional(),
        },
        {
            name: "bar",
            type: "Query",
            schema: z.union([z.literal("1.2"), z.literal("34"), z.literal("-56.789")]).optional(),
        },
    ],
    

    I expected as following:

    parameters: [
        {
            name: "foo",
            type: "Query",
            schema: z.union([z.literal(1), z.literal(-2), z.literal(3)]).optional(),
        },
        {
            name: "bar",
            type: "Query",
            schema: z.union([z.literal(1.2), z.literal(34), z.literal(-56.789)]).optional(),
        },
    ],
    
    opened by macropygia 21
  • feat(error): add support for default error

    feat(error): add support for default error

    Hello Alex,

    With this pull request i'm adding support for default errors. indeed zodios also has default errors.

    This also seems to fix a bug where only other than '200' ok code would not emit response type.

    now the /samples/3.0/petstore.yaml convertion will output correct zodios api definition.

    I added a handlebar helper to handle this use case. tell me if it's ok for you.

    opened by ecyrbe 10
  • Unhashed Schema Names?

    Unhashed Schema Names?

    Is there a way to generate all OpenApi component schemas and export them (without hashed names)?

    In the Pet Store example, there are 8 component schemas. https://github.com/astahmer/openapi-zod-client/blob/main/example/petstore.yaml

    Only 4 of them are added to export const schemas {...}. https://github.com/astahmer/openapi-zod-client/blob/main/example/petstore-schemas.ts

    opened by robotkutya 5
  • TypeError: Cannot read properties of undefined (reading 'startsWith')

    TypeError: Cannot read properties of undefined (reading 'startsWith')

    Huhu,

    getting the following error on openapi-zod-client <file> -a --export-schemas --group-strategy 'tag'. (any group-strategy seems to fail)

    if (schemaName.startsWith("z.")) return;
                                 ^
    
    TypeError: Cannot read properties of undefined (reading 'startsWith')
        at addDependencyIfNeeded (node_modules/openapi-zod-client/dist/generateZodClientFromOpenAPI-8e9ca210.cjs.dev.js:1967:24)
        at node_modules/openapi-zod-client/dist/generateZodClientFromOpenAPI-8e9ca210.cjs.dev.js:1972:16
        at Array.forEach (<anonymous>)
    

    for the same payload as here

    opened by andreas-soroko 3
  • [Question] Why is response z.void() ?

    [Question] Why is response z.void() ?

    When i run: pnpx openapi-zod-client 'https://petstore.swagger.io/v2/swagger.json' -o './src/schemas/test.ts'

    It gives { method: 'get', path: '/pet/:petId', description: Returns a single pet, requestFormat: 'json', parameters: [ { name: 'petId', type: 'Path', schema: z.unknown(), }, ], response: z.void(), errors: [ { status: 400, description: Invalid ID supplied, schema: z.void(), }, { status: 404, description: Pet not found, schema: z.void(), }, ], },

    Why is response: z.void() ?

    opened by drexxdk 2
  • fix(#49): escape control characters

    fix(#49): escape control characters

    related to #49 ref https://github.com/astahmer/openapi-zod-client/issues/49#issuecomment-1345370089

    Targets characters are as follows:

    • TAB, LF and CR
      • \t\n\r
    • C0 Control Codes except for TAB, LF, CR, and Space
      • \x00-\x08\x0B\x0C\x0E-\x1F\x7F
    • C1 Control Codes
      • \x80-\x9F
    • Non-character
      • \uFFFE\uFFFF
    opened by macropygia 2
  • Error: Invalid ref: #/components/schemas/...

    Error: Invalid ref: #/components/schemas/...

    Hi,

    just tried to used it on a existing service and got the error

    Error: Invalid ref: #/components/schemas/NameResolutionType2
    
    {
      ref: '#/components/schemas/NameResolutionType2',
      fallbackName: 'nameResolutionType',
      result: 'NameResolutionType2.optional().default("AssetName")'
    }
    
    

    but don't get it, what is wrong.

    minimal reproduction example:

    {
      "x-generator": "NSwag v13.17.0.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))",
      "openapi": "3.0.0",
      "info": {
        "title": "title",
        "version": "1.5.0.0"
      },
      "paths": {
        "/api/v2/name-resolution/via-id/{assetId}": {
          "get": {
            "tags": [
              "NameResolutionApi"
            ],
            "operationId": "api_v2_name-resolution_via-id_{assetid}_get_ResolveNamesForAssetV2",
            "parameters": [
              {
                "name": "nameResolutionType",
                "in": "query",
                "schema": {
                  "default": "AssetName",
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/NameResolutionType2"
                    }
                  ]
                }
              }
            ],
            "responses": {
              "200": {
                "description": "",
                "content": {
                  "application/json": {
                    "schema": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "400": {
                "description": ""
              },
              "401": {
                "description": "Unauthorized"
              },
              "403": {
                "description": "Forbidden"
              },
              "404": {
                "description": ""
              },
              "500": {
                "description": ""
              }
            }
          }
        }
      },
      "components": {
        "schemas": {
          "NameResolutionType2": {
            "type": "string",
            "description": "",
            "x-enumNames": [
              "AssetName",
              "FullName"
            ],
            "enum": [
              "AssetName",
              "FullName"
            ]
          }
        }
      }
    }
    
    opened by andreas-soroko 2
  • chore(gh-actions): initialize github actions

    chore(gh-actions): initialize github actions

    Hello alex,

    I'm ready to help you. First thing, this PR is just to add github actions to automate your daily process :

    • tests
    • build
    • publish

    Note that publishing requires you to add a github secret named NPM_TOKEN else it wont work. But it's really helpfull to automate this process. You will gain a lot of time by doing this.

    Automatic publish work by just adding a new tag. so just doing :

    npm version <major|minor|patch>
    git push --follow-tags
    

    will create a new release automatically if the CI succeed.

    Another thing, i propose to add (not this PR) is register a renovate bot. this will do all the maintainance for you. A lot of time gained from it. But it as some consequences, like being spammed to update libs when new versions comes out. So it's your choice

    opened by ecyrbe 1
  • TODO - include errors even if content object is empty (auto-infer as z.void())

    TODO - include errors even if content object is empty (auto-infer as z.void())

    this doesn't include errors 400/401/500 cause no MediaTypeObject is set in the ContentObject this should be included nonetheless as if there was one matching mediaType with a schema: z.void()

    paths:
      /v1/stores/{store_id}:
        get:
          tags:
            - store_warehouse
          summary: Get the warehouse linked to a store.
          parameters:
            - name: store_id
              in: path
              description: Please fill the store id
              required: true
              schema:
                type: string
          responses:
            "200":
              description: OK
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/d"
            "400":
              description: Ressource Not Found
              content: {}
            "401":
              description: Not Authorized
              content: {}
            "500":
              description: Methods Not Implemented
              content: {}
    
    opened by astahmer 1
  • TODO - handle OpenAPI `format`

    TODO - handle OpenAPI `format`

    zod implementation: https://github.com/colinhacks/zod#dates

    To write a schema that accepts either a Date or a date string, use z.preprocess.

    const dateSchema = z.preprocess((arg) => {
      if (typeof arg == "string" || arg instanceof Date) return new Date(arg);
    }, z.date());
    type DateSchema = z.infer<typeof dateSchema>;
    // type DateSchema = Date
    
    dateSchema.safeParse(new Date("1/12/22")); // success: true
    dateSchema.safeParse("2022-01-12T00:00:00.000Z"); // success: true
    

    open api spec description: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#data-types

    opened by astahmer 1
  • Unable to run with yarn

    Unable to run with yarn

    Hi, am I doing anything wrong here?

    $ yarn openapi-zod-client plugin-redoc-0.yaml
    
    yarn run v1.22.19
    
    Retrieving OpenAPI document from plugin-redoc-0.yaml
    node:internal/process/promises:288
                triggerUncaughtException(err, true /* fromPromise */);
                ^
    
    [Error: ENOENT: no such file or directory, open './src/template.hbs'] {
      errno: -2,
      code: 'ENOENT',
      syscall: 'open',
      path: './src/template.hbs'
    }
    
    Node.js v18.0.0
    error Command failed with exit code 1.
    info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
    
    opened by 1oglop1 1
  • [Question] Whitelist / Blacklist endpoints?

    [Question] Whitelist / Blacklist endpoints?

    Hi! I was wondering if there was a way to whitelist / blacklist some endpoints / endpoint aliases using regular expressions, and if it made sense in this project. My use case would be the following:

    Say you have to build a small webpage that connects to a really big, old, monolithic API with hundreds of endpoints, but you only had to consume a small subsection of those, say, auth* and analytics. If we had the ability to whitelist and/blacklist, we could run:

    yarn run openapi-zod-client --w "^\/api\/v1\/auth.*$" --w "^\/api\/v1\/analytics.*$"
    yarn run openapi-zod-client --wa "^apiV1Auth.*$" --w "^apiV1Analytics.*$"
    

    (Just a proposal of syntax, regex expresiones may not work)

    To generate a 1000 lines of code instead of 50000 lines, thus making the typescript language server run way faster and reducing out bundle size

    opened by rmortes 1
  • [Question] Inline Enums?

    [Question] Inline Enums?

    Hi! I've just recently started to work with this library, and I'm loving it so far. I have a question about enums.

    I have an endpoint with two GET params:

          - in: query
            name: type
            schema:
              type: string
              enum:
              - One
              - Two
              - Three
          - in: query
            name: o
            schema:
              type: array
              items:
                type: string
                enum:
                - (A - Z)
                - (Z - A)
    

    The type param comes from a Django model with an enum that gets exported to the schema as well:

        TypeEnum:
          enum:
          - ONE
          - TWO
          - THREE
          type: string
    

    As you can see, the enum in typed with the DB values, which are all caps, while the values the API will accept are capitalized. I'm trying to set up a mostly code generated codebase, so I'd love to somehow get api.thatEndPoint.parameters.type.schema.options, but AFAIK this is not a trivial matter.

    I've been somewhat successful .finding my way around api.api to get to the type.schema.options, but this is not typed, so my beautifully typed requests complain of [string, ...string[]] not being of type One | Two | Three, so this is a no go.

    My question is: Is it possible to somehow get the inline enum schema defined in the makeAPi function while retaining typings? And if not, would it be possible to generate and export a zod type for inline enums in the schema?

    Thanks!

    opened by rmortes 5
  • fix(#49): missing zod chains on body/responses

    fix(#49): missing zod chains on body/responses

    https://github.com/astahmer/openapi-zod-client/issues/49#issuecomment-1345644640

    EDIT: I misunderstood the problem and solved another instead, idk if this is needed so i'll leave this PR as it is for now until someone asks for it

    opened by astahmer 1
  • TODO - CI changelog + fix readme on npm

    TODO - CI changelog + fix readme on npm

    opened by astahmer 2
  • TODO - add missing tests for the last patches

    TODO - add missing tests for the last patches

    i'm a bit in a rush atm, havent had the time to had test for this and tried to deliver fixes asap for these:

    https://github.com/astahmer/openapi-zod-client/commit/3675691cc7f3f5ac70299f6be1b60c8b7ba88598 https://github.com/astahmer/openapi-zod-client/commit/17a7091842220091a11d3586cec92299a5d5fbcd

    opened by astahmer 0
Releases(v1.4.15)
Owner
Alexandre Stahmer
Frenchy freelance web/Typescript dev
Alexandre Stahmer
A crash course on Zod - a schema validation library for TypeScript

Zod Crash Course This Zod crash course will give you everything you ever needed to know about Zod - an amazing library for building type-safe AND runt

Total TypeScript 339 Dec 28, 2022
Vite plugin to client bundle i18next locales composited from one to many json/yaml files from one to many libraries. Zero config HMR support included.

vite-plugin-i18next-loader yarn add -D vite-plugin-i18next-loader Vite plugin to client bundle i18next locales composited from one to many json/yaml f

AlienFast 4 Nov 30, 2022
Wrap zod validation errors in user-friendly readable messages

zod-validation-error Wrap zod validation errors in user-friendly readable messages. Features User-friendly readable messages, configurable via options

Causaly 70 Dec 23, 2022
we try to make a tiny p2p client spec, maybe for sigchain gossip thing, maybe for simple blockchain thing

mininode Mininode is a tiny p2p client for prototyping p2p protocols. It is a specification for a set of interfaces that I made to make it easier to t

Nikolai Mushegian 8 Nov 23, 2022
⚡ It is a simplified database module with multiple functions that you can use simultaneously with sqlite, yaml, firebase and json.

Prisma Database Developed with ?? by Roxza ⚡ An easy, open source database ?? Installation npm i prisma.db --save yarn add prisma.db ?? Importing impo

Roxza 21 Jan 3, 2023
Visualize and download JSON / YAML content

Graphize ?? Visualize and download JSON / YAML content in your browser Demo Key Features Preview your JSON / YAML documents instantly Pinch / Scroll Z

Varun A P 25 Dec 26, 2022
A high-resolution local database that uses precise algorithms to easily record data in local files within a project with persistent JSON and YAML support designed to be easy to set up and use

About A high-resolution local database that uses precise algorithms to easily record data in local files within a project with persistent JSON and YML

Shuruhatik 5 Dec 28, 2022
✏️ A small jQuery extension to turn a static HTML table into an editable one. For quickly populating a small table with JSON data, letting the user modify it with validation, and then getting JSON data back out.

jquery-editable-table A small jQuery extension to turn an HTML table editable for fast data entry and validation Demo ?? https://jsfiddle.net/torrobin

Tor 7 Jul 31, 2022
REST API complete test suite using openapi.json

Openapi Test Suite Objective This package aims to solve the following two problems: Maintenance is a big problem to solve in any test suite. As the AP

PLG Works 21 Nov 3, 2022
Easy server-side and client-side validation for FormData, URLSearchParams and JSON data in your Fresh app 🍋

Fresh Validation ??     Easily validate FormData, URLSearchParams and JSON data in your Fresh app server-side or client-side! Validation Fresh Validat

Steven Yung 20 Dec 23, 2022
Example auto-generated OpenAPI client library and an accompanying example Angular app.

To utilize this demo Head into petstore_frontend\petes_pets Run npm install Go to frontend_client_lib\out Run npm install Head back into petstore_fron

Alan Gross 1 Jan 21, 2022
An implementation of the ECMA-419 spec on the Raspberry Pi

raspi-419 An implementation of the ECMA-419 spec on the Raspberry Pi Licsense MIT License Copyright (c) Bryan Hughes Permission is hereby granted, fre

Bryan Hughes 4 Jun 9, 2022
GitHub Action to validate that PR titles in n8n-io/n8n match n8n's version of the Conventional Commits spec

validate-n8n-pull-request-title GitHub Action to validate that PR titles in n8n-io/n8n match n8n's version of the Conventional Commits spec. Setup Cre

Iván Ovejero 2 Oct 7, 2022
RenderIf is a function that receives a validation as a parameter, and if that validation is true, the content passed as children will be displayed. Try it!

RenderIf RenderIf is a function that receives a validation as a parameter, and if that validation is true, the content passed as children will be disp

Oscar Cornejo Aguila 6 Jul 12, 2022
JCS (JSON Canonicalization Scheme), JSON digests, and JSON Merkle hashes

JSON Hash This package contains the following JSON utilties for Deno: digest.ts provides cryptographic hash digests of JSON trees. It guarantee that d

Hong Minhee (洪 民憙) 13 Sep 2, 2022
Package fetcher is a bot messenger which gather npm packages by uploading either a json file (package.json) or a picture representing package.json. To continue...

package-fetcher Ce projet contient un boilerplate pour un bot messenger et l'executable Windows ngrok qui va permettre de créer un tunnel https pour c

AILI Fida Aliotti Christino 2 Mar 29, 2022
Quickly bootstrap your next TypeScript REST API project. Node 16+, auto OpenAPI, Prettier+ESLint, Jest

REST API template with autogenerated OpenAPI Quickly bootstrap your next TypeScript REST API project with the most up to date template. Included a sam

null 6 Oct 1, 2022
Types generator will help user to create TS types from JSON. Just paste your single object JSON the Types generator will auto-generate the interfaces for you. You can give a name for the root object

Types generator Types generator is a utility tool that will help User to create TS Interfaces from JSON. All you have to do is paste your single objec

Vineeth.TR 16 Dec 6, 2022
Prisma 2+ generator to emit Zod schemas from your Prisma schema

Prisma Zod Generator Automatically generate Zod schemas from your Prisma Schema, and use them to validate your API endpoints or any other use you have

Omar Dulaimi 212 Dec 27, 2022