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

@openreply/nestjs-express-openapi

v0.6.4

Published

OpenAPI to NestJS Code Generator and Type Helpers

Readme

NestJS Express OpenAPI Code Generator and Utilities

This package provides you means to use NestJS with an API-first approach. It provides you

  • types for your DTOs for usage in controllers
  • request validation middleware
  • response validation middleware
  • authorization middleware to verify JWT

It is based on the following third party packages

Installation and Usage

Install the package with

npm i -S @openreply/nestjs-express-openapi

First, write a regular openapi.yaml and place it next to your NestJS application's package.json. Then add the following scripts to your package.json:

{
  "scripts": {
    "generate": "openapi-to-typescript compile openapi.yaml generated/openapi.ts"
  }
}

And run npm run generate. This will compile the openapi.yaml to the generated/openapi.ts which gives you TypeScript types.

Then, configure the middlewares for OpenAPI and, if needed, JWT in your app.module.ts:

import { Module } from '@nestjs/common';
import { OpenapiMiddlewareModule } from '@openreply/nestjs-express-openapi';
import { AppController } from './app.controller.js';
import { AppService } from './app.service.js';
import { requireAllPrivileges } from './security-handler-privileges.js';

// Import the bundled API specification from the generated file
import { apiSpec } from '../generated/openapi.js';

@Module({
  imports: [
    OpenapiMiddlewareModule.register({
      apiSpec,
      jwtVerifier: { // optional JWT signature verification; default: undefined
        // DO NOT USE this config for production. Use more secure settings like "RS512" with a key pair
        secret: 'my-secret',
        algorithms: ['HS512'],
      },
      openapiValidator: {
        ignorePaths: /^\/(api\/)?health(\/.*)?/, // ignore all health check paths if you do not want to have them in your openapi.yaml
        validateSecurity: { // optional JWT payload validation
          handlers: {
            // the keys of this object must match the `securitySchemes` names in the openapi.yaml
            // see the example in the examples/esm/ directory
            requireAllPrivileges: (req, res, next) => { /* do some checks here */ return true /* or false */ },
          },
        },
      },
      normalizeMediaTypes: false, // optionally normalize media types; default: false
      serveOpenapiDocs: { path: '/api-docs' }, // optional endpoint to serve the OpenAPI specification as JSON; default: undefined
      serveSwaggerUi: { path: '/swagger-ui' }, // optional endpoint to serve the Swagger UI; default: undefined
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Finally, activate the middlewares in the main.ts that holds your bootstrap() function:

import { NestFactory } from '@nestjs/core';
import { OPENAPI_MIDDLEWARE } from '@openreply/nestjs-express-openapi';
import { type RequestHandler, json } from 'express';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    // Body parsing is done by express middleware
    bodyParser: false,
  });

  app.use(
    // Install a JSON body parser
    // This one is provided by express, but you can use a different one
    // or additional ones (for msgpack for example) if you like as long
    // as you install them before the validator middleware
    json({ type: ['json', '*/*+json'] }),
    app.get<RequestHandler>(OPENAPI_MIDDLEWARE),
  );
  await app.listen(3000);
}
bootstrap();

This activates request and response validation. No need for class-validator or class-transformer. No need for guards that check for json web token clams.

You can use the Type utilities to get easier access to the generated types.

CLI Options

The CLI is supposed to be called as in

npx openapi-to-typescript compile openApiInputFile typescriptOutputFile

which reads the openApiInputFile (JSON or YAML) and generates a typeScriptOutputFile.

The output contains the types of the API and the API specification as a TypeScript object.

There are several options to customize the output. Run the following for a detailed list:

npx openapi-to-typescript compile --help

In the following, we describe only the most notable:

--preserve-x-extensible-enum (default: false)

If you apply Zalando RESTful API and Event Guidelines, you are not using enum in the API, but x-extensible-enum. This is great for documentation, but it hinders development a little. Therefore, the output contains enum instead of x-extensible-enum. This affects the types as well as request/response validation. You can keep x-extensible-enum which allows for any string to be used by passing the --preserve-x-extensible-enum parameter.

JS API Options

apiSpec (Required)

This is the openapi.yaml as a structured type. You can just pass the generated specification from the above mentioned generate script.

jwtVerifier (Optional)

If set, each request that is annotated with security will be checked for a valid signature in its JWT. By default, it assumes a JWT to be in the Authorization header, prefixed with Bearer . On successful verification, the payload will be set in req.auth. On unsuccessful verification, it will deny the request.

openapiValidator (Optional)

The OpenAPI validating middleware is always active. Each request and each response will be validated against the API specification by default. You can pass the options of the express-openapi-validator directly here. Note that the apiSpec is already passed to the express-openapi-validator by this module. Note also that this default configuration is set:

{
  validateApiSpec: true,
  validateRequests: true,
  validateResponses: true,
  validateSecurity: true,
  validateFormats: true,
  ignoreUndocumented: false,
}

openapiValidator.validateSecurity.handlers (Optional)

If you enable the jwtVerifier, you might also want to enable security handlers. These check each request that is annotated with security for a matching JWT payload in the middleware. This can be set up with openapiValidator.validateSecurity.handlers in a central place.

See the example applications in examples/ for security handlers that check the JWT payload for custom features.

normalizeMediaTypes (Optional)

If set to true, this will modify your OpenAPI specification that is used for validation and will modify each request's Accept/Content-Type headers to have normalized media types:

Example of such modifications:

  • adding spaces for parameters: application/vnd.foo.fetch+json;version=1 => application/vnd.foo.fetch+json; version=1

Some HTTP clients normalize the media types when they send requests without developers having a way to change it. Since the validation logic of the middleware compares these for equality, such client-side modifications can break functionality. If you want to support such clients that normalize before sending the request, enable this option.

Be aware that the modified specification only affects the internal validation logic but not the generated types in openapi.ts. Be aware that the actual requests and responses are modified.

serveOpenapiDocs (Optional)

This option sets up an endpoint to serve the OpenAPI specification as JSON. If you set serveOpenapiDocs: { path: "/api-docs" } you can fetch the API spec:

❯ curl 'localhost:3000/api-docs'
# => {"openapi":"3.1.0","info":{"title":"api-first-project","summary":...

serveSwaggerUi (Optional)

If you set serveSwaggerUi: { path: '/swagger-ui' }, a HTML representation of the API documentation (Swagger UI) is served at /swagger-ui. The serveSwaggerUi option accepts all options of the swagger-ui-express package.

Type Utilities

Use the provided type utilities to extract the types from the generated/openapi.ts:

import type {
  PathParameters,
  QueryParameters,
  RequestBody,
  RequestHeaders,
  ResponseBody,
} from "../generated/openapi.js";

This type will give you the query parameters that are specified at GET /greetings:

type GetDatesQuery = QueryParameters<{ method: 'get'; path: '/greetings' }>;
/* => type GetDatesQuery = {
    name?: string;
} | undefined */

This type will give you the path parameters that are specified at GET /dates/{time}:

type GetDatesParams = PathParameters<{ method: 'get'; path: '/dates/{time}' }>;
/* => type GetDatesParams = {
    time: "today" | "yesterday";
} */

Note that the RequestBody requires an additional contentType property: This type will give you the request body type that is specified at POST /authentications for the application/x.credentials.v1+json request-content-type:

type AuthenticationBody = RequestBody<{ method: 'post'; path: '/authentications'; contentType: 'application/x.credentials.v1+json' }>;
/* => type AuthenticationBody = {
    username: string;
    password: string;
} */

Please also note that ResponseBody requires a status property in the second type argument:

type TokenResponse = ResponseBody<{ method: 'post'; path: '/authentications'; status: 200 } >;
/* => type TokenResponse = {
    access_token: string;
    scheme: "bearer";
} */

Example Code

See the examples/ directory for full example NestJS applications. One application is built for CommonJS with the regular tools for NestJS. The other is built for ESM using SWC and allows you to use the modern JavaScript type system and hence use the most modern npm packages.

In any of the examples, run npm ci and npm run generate to compile the generated/openapi.ts. Then, run npm run build and npm run start to start the application.

You can send requests like the following to test the controllers:

curl 'localhost:3000/greetings'
# => Hello, World!

curl 'localhost:3000/greetings?name=Alice'
# => Hello, Alice!

curl -i -X 'POST' \
  'http://localhost:3000/authentications?privileges=date%3aread&privileges=date%3awrite&privileges=test' \
  -H 'accept: application/x.access-token.v1+json' \
  -H 'Content-Type: application/x.credentials.v1+json' \
  -d '{
  "username": "user",
  "password": "pass"
}'
# =>
# HTTP/1.1 200 OK
# X-Powered-By: Express
# Content-Type: text/html; charset=utf-8
# Content-Length: 271
# ETag: W/"10f-lV3R+oO7LYT+4FGAy3uRUZn6tWk"
# Date: Mon, 12 May 2025 19:49:07 GMT
# Connection: keep-alive
# Keep-Alive: timeout=5
#
# {"access_token":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NDcwNzkzNDcsImV4cCI6MTc0NzExNTM0NywicHJpdmlsZWdlcyI6WyJkYXRlOnJlYWQiLCJkYXRlOndyaXRlIiwidGVzdCJdfQ.Rtm8nPo9T25MrRUHlQKGiWKoYCFcMu5oJ-41bZZ6S_uiKS4cEG7DJEonHSmBSodHIMpUxYDGbsg7wy3I2eZWBQ","scheme":"bearer"}

curl -i -X 'POST' \
  'http://localhost:3000/authentications?privileges=date%3aread&privileges=date%3awrite&privileges=test' \
  -H 'accept: application/x.access-token.v1+json' \
  -H 'Content-Type: application/x.credentials.v2+json' \
  -d '{
  "user": "user",
  "pass": "pass"
}'
# =>
# HTTP/1.1 200 OK
# X-Powered-By: Express
# Content-Type: text/html; charset=utf-8
# Content-Length: 271
# ETag: W/"10f-ogAvCUyaRBOhd0wecjp3M1jfs8Q"
# Date: Mon, 12 May 2025 19:49:42 GMT
# Connection: keep-alive
# Keep-Alive: timeout=5
#
# {"access_token":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NDcwNzkzODIsImV4cCI6MTc0NzExNTM4MiwicHJpdmlsZWdlcyI6WyJkYXRlOnJlYWQiLCJkYXRlOndyaXRlIiwidGVzdCJdfQ.NupQ-iKf5oQOTjnLnU-YWTnh3KWPcy7EHzqmK0dyG1kwl8Jb_2kuypxUj_Qofy8QMZw_HBVyWsvQzCakoLSKIg","scheme":"bearer"}

# this should give you a HTTP 400:
❯ curl -i -X 'POST' \
  'http://localhost:3000/authentications?privileges=date%3aread&privileges=date%3awrite&privileges=test' \
  -H 'accept: application/x.access-token.v1+json' \
  -H 'Content-Type: application/x.credentials.v2+json' \
  -d '{
  "username": "user",
  "pass": "pass"
}'
# =>
# HTTP/1.1 400 Bad Request
# X-Powered-By: Express
# Content-Type: application/json; charset=utf-8
# Content-Length: 937
# ETag: W/"3a9-sLLSdpfxKxixbdOkZJyOsPlQ7Uw"
# Date: Wed, 21 May 2025 13:27:23 GMT
# Connection: keep-alive
# Keep-Alive: timeout=5
#
# {"message":"request/body must have required property 'user'","status":400,"errors":[{"path":"/body/user","message":"must have required property 'user'","errorCode":"required.openapi.validation"}],"stack":"Bad Request: request/body must have required property 'user'\n    at Object.POST-/authentications-application/x.credentials.v2+json (/Volumes/OpenReply/Tools/nestjs-express-openapi/node_modules/express-openapi-validator/dist/middlewares/openapi.request.validator.js:134:31)\n    at RequestValidator.validate (/Volumes/OpenReply/Tools/nestjs-express-openapi/node_modules/express-openapi-validator/dist/middlewares/openapi.request.validator.js:48:41)\n    at /Volumes/OpenReply/Tools/nestjs-express-openapi/node_modules/express-openapi-validator/dist/openapi.validator.js:233:53\n    at /Volumes/OpenReply/Tools/nestjs-express-openapi/node_modules/express-openapi-validator/dist/openapi.validator.js:165:28","path":"/authentications"}

OK_TOKEN=$(curl -s -X 'POST' \
  'http://localhost:3000/authentications?privileges=date%3aread&privileges=something%3aelse&privileges=test' \
  -H 'accept: application/x.access-token.v1+json' \
  -H 'Content-Type: application/x.credentials.v2+json' \
  -d '{
  "user": "user",
  "pass": "pass"
}' | jq -r '.access_token')
curl 'localhost:3000/dates/yesterday' -H "Authorization: Bearer $OK_TOKEN"
# => {"date":"2025-05-11"}

INSUFFICIENT_TOKEN=$(curl -s -X 'POST' \
  'http://localhost:3000/authentications?privileges=date%3aread&privileges=something%3aelse' \
  -H 'accept: application/x.access-token.v1+json' \
  -H 'Content-Type: application/x.credentials.v2+json' \
  -d '{
  "user": "user",
  "pass": "pass"
}' | jq -r '.access_token')
curl 'localhost:3000/dates/yesterday' -H "Authorization: Bearer $INSUFFICIENT_TOKEN"
# => {"message":"You have insufficient privileges to access this resource","status":403,"errors":[{"path":"/dates/{time}","message":"You have insufficient privileges to access this resource"}]}