npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

express-openapi-zod

v1.0.0

Published

## Install

Downloads

18

Readme

express-openapi-zod

Install

npm install express-openapi-zod @asteasolutions/zod-to-openapi

Contents

Purpose

  • Generate openapi specification from express routers and zod schemas.
  • Add types for express handler Request and Response objects.

Usage

Please check out the zod-to-openapi setup first. express-openapi-zod will automatically registers the openapi paths based on the express routes, but you must configure the rest of zod-to-openapi yourself. See the demo for a working example.

Create an OpenAPIRouter

Use OpenAPIRouter in place of express.Router:

import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";
import { OpenAPIRouter } from "express-openapi-zod";

const registry = new OpenAPIRegistry();
const router = OpenAPIRouter(registry);

Use openapi()

The openapi function registers the path for openapi generation, and provides full typing to the express Request and Response objects in the chained delete, get, patch, post, and put calls.

router.openapi({
  /*zod-to-openapi registerPath config*/
}).get("/", (req, res) => {
  /*`req` and `res` are fully typed*/
}).

Then, generate the openapi specification using zod-to-openapi.

Example

router
  .openapi({
    path: "/pets",
    description: "Get all pets"
    request: {
      query: z.object({
        color: z.optional(z.string()).openapi({ description: 'Get only pets with this color', example: "grey" }),
      }),
    },
    responses: {
      200: {
        description: "OK",
        content: {
          "application/json": {
            schema: z.array(
              z.object({
                name: z.string().openapi({ example: "Mittens" }),
                color: z.string().openapi({ example: "black" }),
              })
            )
          }
        }
      },
    },
  })
  .post("", (req, res) => {
    /**
     * typeof req.query = {
     *   color?: string
     * }
     */
    const pets = getPets({ color: req.query.color });
    /**
     * res.json() typeof input = Array<{
     *   name: string;
     *   color: string
     * }>
     */
    res.json(pets);
  });

The above would generate the following openapi path:

"/pets":
  get:
    description: Get all pets
    parameters:
      - in: query
        name: color
        schema:
          type: string
          description: "Get only pets with this color"
          example: "grey"
        required: false
    responses:
      "200":
        description: OK
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: Mittens
                color:
                  type: string
                  example: grey
              required:
                - name
                - color
      "204":
        description: No content

OpenAPIRouter()

import { OpenAPIRouter } from "express-openapi-zod";

router = OpenAPIRouter(
  // A zod-to-openapi registry
  registry: OpenAPIRegistry,
  // An express.Router instance to use
  router?: Router,
  options?: {
    // If no media type given, these are used
    defaultRequestBodyMediaTypes: ["application/json"],
    defaultResponseBodyMediaTypes: ["application/json"],
  }
)

Accessing express router

You can access the underlying express.Router through the router property.

export default router.router; // express.Router

The OpenAPIRouter cannot be used with express().use(). You must use the underlying express router, either via the router property, or passing the router in the constructor.

openapi()

OpenAPIRouter.openapi() takes the same configuration object as the zod-to-openapi registerPath function.

Reduced forms

To reduce the amount of duplication and boilerplate - particularly in cases where your API generally consumes and produces the same media types (such as application/json) - you may supply a z.ZodType directly to the request.body or responses[*] fields instead of the full registerPath configuration object:

For example, this:

router.openapi({
  /*...*/
  request: {
    body: {
      content: {
        "application/json": {
          schema: CreateUserBodySchema,
        },
      },
    },
  },
  responses: {
    200: {
      description: "OK",
      content: {
        "application/json": {
          schema: UserSchema,
        },
      },
    },
  },
});

and this:

router.openapi({
  /*...*/
  request: {
    body: {
      schema: CreateUserBodySchema,
    },
  },
  responses: {
    200: {
      description: "OK",
      schema: UserSchema,
    },
  },
});

and this:

router.openapi({
  /*...*/
  request: {
    body: CreateUserBodySchema,
  },
  responses: {
    200: UserSchema, // 'description' autogenerated. "OK" in this case
  },
});

are all equivalent.

You may also supply null to responses[*] if there is no response body, but you still want to register a response:

router.openapi({
  /*...*/
  responses: {
    200: {
      description: "OK",
    },
    // is the same as:
    200: null,
  },
});

Content media types for reduced forms

When the content media types are not specified, they will fallback to the defaultRequestBodyMediaTypes and defaultResponseBodyMediaTypes options given to the OpenAPIRouter()

const router = OpenAPIRouter(registry, router, {
  defaultRequestBodyMediaTypes: ['application/xml','application/json']
  defaultResponseBodyMediaTypes: ['application/csv']
})
router.openapi({
  /*...*/
  requests: {
    body: CreateUserBodySchema, // registered both `application/xml` and `application/json`
  }
  responses: {
    200: TabularData, // registered as `application/csv` in openapi `responses`
  },
});

Gotcha with typed unions

See the following:

const A = z.object({ id: z.string() });
const B = z.object({ id: z.string(), name: z.string() });

router
  .openapi({
    /*...*/
    responses: {
      200: z.union(A, B),
    },
  })
  .get((req, res) => {
    /**
     * res.json() typeof input = {
     *   id: string;
     * }
     */
  });

The type has been reduced to { id: string }, instead of the expected { id: string } | { id: string, name: string }, due to the 'excess property checking' typescript feature.

To get the expected type, pass an array instead:

router
  .openapi({
    /*...*/
    responses: {
      200: [A, B],
    },
  })
  .get((req, res) => {
    /**
     * res.json() typeof input = {
     *   id: string;
     * } | {
     *   id: string;
     *   name: string;
     * }
     */
  });