Giter Club home page Giter Club logo

express-openapi's Introduction

Express OpenAPI

NPM Version NPM Downloads js-standard-style

A middleware for generating and validating OpenAPI documentation from an Express app.

This middleware will look at the routes defined in your app and fill in as much as it can about them into an OpenAPI document. Optionally you can also flesh out request and response schemas, parameters, and other parts of your api spec with path specific middleware. The final document will be exposed as json served by the main middleware (along with component specific documents).

Note on package name

This package documents itself as @express/openapi. This is because we (the Express TC) have been discussing adopting the npm scope for publishing "core maintained" middleware modules. This is one such middleware. While we are working out the details of this I am publishing this module under my personal scope. When that is resolved we will move it over to the main scope and I will deprecate this module.

Install & usage step for now: $ npm i @wesleytodd/openapi & const openapi = require('@wesleytodd/openapi')

Philosophy

It is common in the OpenAPI community to talk about generating code from documentation. There is value in this approach, as often it is easier for devs to let someone else make the implementation decisions for them. For me, I feel the opposite. I am an engineer whose job it is to make good decisions about writing quality code. I want control of my application, and I want to write code. With this module I can both write great code, as well as have great documentation!

Installation

$ npm install --save @express/openapi

Usage

const openapi = require('@express/openapi')
const app = require('express')()

const oapi = openapi({
  openapi: '3.0.0',
  info: {
    title: 'Express Application',
    description: 'Generated docs from an Express api',
    version: '1.0.0',
  }
})

// This will serve the generated json document(s)
// (as well as swagger-ui or redoc if configured)
app.use(oapi)

// To add path specific schema you can use the .path middleware
app.get('/', oapi.path({
  responses: {
    200: {
      description: 'Successful response',
      content: {
        'application/json': {
          schema: {
            type: 'object',
            properties: {
              hello: { type: 'string' }
            }
          }
        }
      }
    }
  }
}), (req, res) => {
  res.json({
    hello: 'world'
  })
})

app.listen(8080)

In the above example you can see the output of the OpenAPI spec by requesting /openapi.json.

$ curl -s http://localhost:8080/openapi.json | jq .
{
  "openapi": "3.0.0",
  "info": {
    "title": "Express Application",
    "version": "1.0.0",
    "description": "Generated docs from an Express api"
  },
  "paths": {
    "/": {
      "get": {
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hello": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Api Docs

openapi([route [, document[, options]]])

Creates an instance of the documentation middleware. The function that is returned is a middleware function decorated with helper methods for setting up the api documentation.

Options:

  • route <string>: A route for which the documentation will be served at
  • document <object>: Base document on top of which the paths will be added
  • options <object>: Options object
Coerce

By default coerceTypes is set to true for AJV, but a copy of the req data is passed to prevent modifying the req in an unexpected way. This is because the coerceTypes option in (AJV modifies the input)[ajv-validator/ajv#549]. If this is the behavior you want, you can pass true for this and a copy will not be made. This will result in params in the path or query with type number will be converted to numbers based on the rules from AJV.

OpenApiMiddleware.path([definition])

Registers a path with the OpenAPI document. The path definition is an OperationObject with all of the information about the requests and responses on that route. It returns a middleware function which can be used in an express app.

Example:

app.get('/:foo', oapi.path({
  description: 'Get a foo',
  responses: {
    200: {
      content: {
        'application/json': {
          schema: {
            type: 'object',
            properties: {
              foo: { type: 'string' }
            }
          }
        }
      }
    }
  }
}), (req, res) => {
  res.json({
    foo: req.params.foo
  })
})

OpenApiMiddleware.validPath([definition])

Registers a path with the OpenAPI document, also ensures incoming requests are valid against the schema. The path definition is an OperationObject with all of the information about the requests and responses on that route. It returns a middleware function which can be used in an express app and will call `next(err) if the incoming request is invalid.

The error is created with (http-errors)[https://www.npmjs.com/package/http-errors], and then is augmented with information about the schema and validation errors. Validation uses (avj)[https://www.npmjs.com/package/ajv], and err.validationErrors is the format exposed by that package.

Example:

app.get('/:foo', oapi.validPath({
  description: 'Get a foo',
  responses: {
    200: {
      content: {
        'application/json': {
          schema: {
            type: 'object',
            properties: {
              foo: { type: 'string' }
            }
          }
        }
      }
    },
    400: {
      content: {
        'application/json': {
          schema: {
            type: 'object',
            properties: {
              error: { type: 'string' }
            }
          }
        }
      }
    }
  }
}), (err, req, res, next) => {
  res.status(err.status).json({
    error: err.message,
    validation: err.validationErrors,
    schema: err.validationSchema
  })
})

OpenApiMiddleware.component(type[, name[, definition]])

Defines a new Component on the document.

Example:

oapi.component('examples', 'FooExample', {
  summary: 'An example of foo',
  value: 'bar'
})

If neither definition nor name are passed, the function will return the full components json.

Example:

oapi.component('examples', FooExample)
// { '$ref': '#/components/examples/FooExample' }

If name is defined but definition is not, it will return a Reference Object pointing to the component by that name.

Example:

oapi.component('examples')
// { summary: 'An example of foo', value: 'bar' }

OpenApiMiddleware.schema(name[, definition])

OpenApiMiddleware.response(name[, definition])

OpenApiMiddleware.parameters(name[, definition])

OpenApiMiddleware.examples(name[, definition])

OpenApiMiddleware.requestBodies(name[, definition])

OpenApiMiddleware.headers(name[, definition])

OpenApiMiddleware.securitySchemes(name[, definition])

OpenApiMiddleware.links(name[, definition])

OpenApiMiddleware.callbacks(name[, definition])

There are special component middleware for all of the types of component defined in the OpenAPI spec. Each of which is just the component method with a bound type, and behave with the same variadic behavior.

OpenApiMiddleware.redoc()

OpenApiMiddleware.swaggerui()

Serve an interactive UI for exploring the OpenAPI document.

Redoc and SwaggerUI are two of the most popular tools for viewing OpenAPI documents and are bundled with the middleware. They are not turned on by default but can be with the option mentioned above or by using one of these middleware.

Example:

app.use('/redoc', oapi.redoc)
app.use('/swaggerui', oapi.swaggerui)

express-openapi's People

Contributors

armand1m avatar enov avatar kinabalu avatar mecode4food avatar megapixel99 avatar r3na avatar thomasheartman avatar wesleytodd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

express-openapi's Issues

Use With Express Router

Can this be used with an Express Router? If so, can you share an example and/or update the documentation?

Using with router in node submodules doesn't show up in json

I've got a test project here which is small: https://github.com/kinabalu/express-openapi-router-test

I've got a setup where the directory structure is

./routes/<module>/index.js and we use a router for all the endpoints in that /module like shown in the example. Only problem is, when using oapi.path it seems to call things in the express-openapi codebase, but things get added later and never get added to the openapi.json. Not sure if I have some order done incorrectly?

Obtaining openapi.yaml instead of openapi.json

Hi, often when I am trying to share the openapi specs, I have to go to localhost:8080/openapi.json, grab the json, convert it to a YAML format, and then share it.

It seems like a lot of places that use OpenAPI tend to accept it in YAML format instead of json.

Do you think by any chance that there is a way to do so from within the express server itself?

Get generated OpenAPI document

I need to get the generated document schema.

I'm trying to get it from oapi.document, but it doesn't contain path information.

const openapi = require('@wesleytodd/openapi')
const app = require('express')()

const oapi = openapi({
  openapi: '3.0.0',
  info: {
    title: 'Express Application',
    description: 'Generated docs from an Express api',
    version: '1.0.0',
  }
})

app.use(oapi)

app.get('/', oapi.path({
  responses: {
    200: {
      description: 'Successful response',
      content: {
        'application/json': {
          schema: {
            type: 'object',
            properties: {
              hello: { type: 'string' }
            }
          }
        }
      }
    }
  }
}), (req, res) => {
  res.json({
    hello: 'world'
  })
})

console.log(oapi.document)

result:

{
  openapi: '3.0.0',
  info: {
    title: 'Express Application',
    version: '1.0.0',
    description: 'Generated docs from an Express api'
  },
  paths: {}
}

Any solutions?

library is not picking up the paths in my express application

My openapi.helper.ts file (which is located in: src/helpers/:

import oapi from '@wesleytodd/openapi';

const openapi = oapi({
  openapi: '1.0.0',
  info: {
    title: 'Rest Docs',
    version: '1.0.0',
    description: 'Rest description,
  },
});

export default openapi;

Then inside my express app configuration file located in: src/app.ts, I am passing the openapi export to the app.use:

Note my routes are on v1/

app.use('/v1/', routes);
app.use(openapi);

I also tried:

app.use('/v1/', openapi, routes);

I also tried adding adding openapi to my routes export. routes.use(openapi) but it still couldn't pick up the paths, like so:

routes.ts:

routes.use(`/myrouter`, openapi, farmRouter);

When trying to visit the <path>/openapi.json, it always returns an empty 'paths' object.

{
    "openapi": "1.0.0",
    "info": {
       title: 'Rest Docs',
    version: '1.0.0',
    description: 'Rest description,
     },
    "paths": {}
}

The libs I am using are:

I am using nodejs 16 & "express": "^4.17.1",, "@wesleytodd/openapi": "^0.1.0",

Release 1.0.0

Just wanted to put together a quick 1.0.0 plan. We have two breaking changes lined up:

I was thinking maybe we should quickly tackle a few of the open issues as well before we release?

  • #20: This one might require some work adding a test that covers it. (@AustinGil not sure if you are still using the package and/or want to help)
  • #25: This should be relatively easy. If we wanted we could even generate the .d.ts files and test them (ex pkgjs/nv#15).
  • #22: Also TS related, not sure I remember enough about this one to be sure what we need to do or if it is a reasonable ask.
  • #19: This one maybe @AustinGil could comment on as well? Do we want this?

Maybe we don't need to land all this, most of these are non breaking and could go out after 1.0.0, but just wanted to see what folks thought.

cc @Megapixel99 @enov @thomasheartman

Change openapi.json base URL

I have an Express API that sites behind an NGINX proxy server. The API runs on a child route (/api). So all my express routes are defined as /api/some-route.

I tried setting up the redoc UI with app.use('/api/redoc', oapi.redoc);. When I go to /api/redoc I get a UI that I assume is redoc, but it has an error saying

Something went wrong...
"https://localhost:3000/openapi.json" is not a valid JSON Schema
Stack trace
SyntaxError: "https://localhost:3000/openapi.json" is not a valid JSON Schema
    at Function.syntax (https://localhost-core/napi/redoc/redoc.standalone.js:29:28051)
    at https://localhost-core/napi/redoc/redoc.standalone.js:41:54299

ReDoc Version: 2.0.0-rc.45
Commit: aa53416d

From there, I've tried app.use('/api', oapi);. Then /api/openapi.json works fine, but the redoc UI is still trying to get the json at /openapi.json

Is there a way to tell the UI to look at the prefixed path instead?

Hosting a service not at root causes incorrect path generation

Hey! First off: thanks a lot for this package; it's a key part of how we've set up our OpenAPI integration with express 🙏🏼 However, I've come across an issue that I'm not sure how to fix.

Problem statement

When running a server with this package on a path (not root domain level), the path to the server gets added to all paths in the spec.

To illustrate: at Unleash, we run different instances of hosted Unleash servers at subpaths, such as https://app.unleash-hosted.com/demo, https://app.unleash-hosted.com/instance2, https://app.unleash-hosted.com/instance3 and so forth.

When generating the path spec using validPath, the path to the server gets prepended to the path, so all paths start with the path to the server (e.g. /demo/api/...).

The image below demonstrates what it looks like in the Swagger UI. Note that the server URL is also incorrect (missing the path to the instance), but that is our fault and being fixed.

Swagger UI with paths prepended by path to server

All paths start with /demo, but that's the actual Unleash instance and should not be included as part of the path. Instead, the server URL should include /demo and the paths should start with /api or whatever else their prefix is.

Root cause

I've done a little bit of digging, and it seems that the validPath function uses AJV under the hood (as documented) and returns a function that can be used as express middleware.

Inspecting the requests that come in, I think that the issue lies with the fact that req.baseUrl (and similar fields) all contain the path to the instance as the first section. This makes sense of course, considering that the request is made to that url. But it becomes an issue in situations like ours.


So my question is: is there a way to work around this? Is it something we could fix? It's possible that we've just missed a configuration setting (though I do think I've checked everything that's in the readme, I might be wrong 💁🏼).

Cheers!

npm update/install fails because of redoc old version

Hello,

I tried to do a npm update yesterday but it failed : the command is stuck in the idealTree part. (it's the same with an npm install if I don't have any package-lock.json)
It seems this package is the culprit... to be specific I tried to check the dependancies and installing redoc on my own dependancies seems to resolve the problem.
Maybe updating the dependancies in this package would solve my problem.

So my questions are :
1/ Am I the only one with this problem ?
2/ Would it be possible to update de the dependancies for the npm package ? It would also solve moderate severity vulnerabilities reported by npm.
3/ I know that swagger & redoc are very useful but wouldn't it be more convenient to let user decide if they want to install one or the other and not forcing to install them along the lib ?

Thanks in advance,

I can't install @express/openapi package

I ran the command 'npm install --save @express/openapi' to install this package.
but, npm returned 404 Not Found response.
(@express/openapi is not in the npm registry)

So, How can i install this package?

Use definitions and references within the same route

I have a route that returns an object with a few properties. Each of these properties is another object with a few props. All of these nested objects follow the same schema, it's just the values that change, so I thought a definition and reference would be the best way to represent them.

https://cswr.github.io/JsonSchema/spec/definitions_references/

However, when I tried to implement them the way described in the spec, it did not work. The only example I see of refs in this project's docs are if you use oapi.component. I didn't think that was a great approach for my use case as these are not really global objects I want to make available.

Is this production ready?

I would like to know if this is usable on production, at my company we don't care too much about 100% stability, but some stability is kinda good, Is this stable enough to be used on production environment?

Bug with dynamic route and nested routers

For some reason, I'm getting strange error while using this package with a nested router on a dynamic route. This is the error:

TypeError: split(...).join is not a function
    at /path/to/node_modules/@wesleytodd/openapi/lib/generate-doc.js:73:47
    at Array.forEach (<anonymous>)
    at iterateStack (/path/to/node_modules/@wesleytodd/openapi/lib/generate-doc.js:71:24)
    at /path/to/node_modules/@wesleytodd/openapi/lib/generate-doc.js:73:7
    at Array.forEach (<anonymous>)
    at iterateStack (/path/to/node_modules/@wesleytodd/openapi/lib/generate-doc.js:71:24)
    at /path/to/node_modules/@wesleytodd/openapi/lib/generate-doc.js:73:7
    at Array.forEach (<anonymous>)
    at iterateStack (/path/to/node_modules/@wesleytodd/openapi/lib/generate-doc.js:71:24)
    at /path/to/node_modules/@wesleytodd/openapi/lib/generate-doc.js:17:5

The base router defines a dynamic path and requires a nested router to handle that path. It looks more or less like this:

// router.js
const routes = require('express').Router();

routes.use('/:id', require('./_id'));

module.exports = routes;

The OpenAPI spec is used as a middleware on that nested router's base path like so:

// _id.js
const router = require('express').Router({ mergeParams: true });
const { openapi } = require('../../../middleware');

router.get(
  '/',
  openapi.validPath({
    summary: 'Get a user.',
    parameters: [
      {
        in: 'path',
        imageId: 'id',
        schema: {
          type: 'integer',
        },
      },
    ],
    responses: {
      200: {
        content: {
          'application/json': {
            schema: {
              type: 'string',
            },
          },
        },
      },
    },
  }),
  async (req, res) => {
    res.send('done')
  }
);

module.exports = router;

If I completely remove the OpenAPI references, the application works as expected. The application also works as expected if I define the dynamic route handler in the same file as the router. It seems to only have an issue if I try using a nested router.

Webpack fail

Hello,

I am trying to use webpack to pack my web application.
And I get following error messages here. It seems function serveRedoc does not work well with webpack. I attached my example project.

In the attached project, I run:

npm install
npx webpack
node build/index-bundle.js

internal/validators.js:125
    throw new ERR_INVALID_ARG_TYPE(name, 'string', value);
    ^

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type number
    at validateString (internal/validators.js:125:11)
    at Object.resolve (path.js:161:7)
    at Object.e.exports.serveRedoc (C:\Users\liangyi\Desktop\nodeProject\oapi\build\index-bundle.js:1:4883)
    at e.exports (C:\Users\liangyi\Desktop\nodeProject\oapi\build\index-bundle.js:1:3048)
    at Object.<anonymous> (C:\Users\liangyi\Desktop\nodeProject\oapi\build\index-bundle.js:1:1206)
    at t (C:\Users\liangyi\Desktop\nodeProject\oapi\build\index-bundle.js:1:110)
    at C:\Users\liangyi\Desktop\nodeProject\oapi\build\index-bundle.js:1:902
    at Object.<anonymous> (C:\Users\liangyi\Desktop\nodeProject\oapi\build\index-bundle.js:1:911)
    at Module._compile (internal/modules/cjs/loader.js:778:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)

Here is my webpack.config.js

const path = require('path');
const fs = require('fs');

const nodeModules = {};
fs.readdirSync('node_modules')
    .filter(function (x) {
        return ['.bin'].indexOf(x) === -1;
    })
    .forEach(function (mod) {
        nodeModules[mod] = 'commonjs ' + mod;
    });


module.exports = {
    entry: './index.js',
    target: 'node',
    output: {
        path: path.join(__dirname, 'build'),
        filename: 'index-bundle.js'
    },
    externals: nodeModules
};

Could you help me with this case?

oapi.zip

Best regard,
Yiqing Liang

Lots of Content Security Policy errors

Im not sure if this is due to recent browser versions, but for both the Redoc and Swagger UI pages I'm having issues. Neither will load in Chrome, and only Redoc will load in Firefox. Here are the console errors:

Redoc in Chrome:

SearchWorker.worker.ts:5 Refused to create a worker from 'blob:https://localhost-core/4c437f09-96dd-4adf-a282-f7cd85221a42' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'worker-src' was not explicitly set, so 'script-src' is used as a fallback.

Failed to construct 'Worker': Access to the script at 'blob:https://localhost-core/4c437f09-96dd-4adf-a282-f7cd85221a42' is denied by the document's Content Security Policy.

Swagger UI in Chrome:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-7LFgTyTwypFqRNwknDs4bBXRSqR6QY7uIOSKAtjoLKE='), or a nonce ('nonce-...') is required to enable inline execution.

Redoc in Firefox:

Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”). onloadwff.js:71:799505
Content Security Policy: The page’s settings blocked the loading of a resource at eval (“script-src”).
Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”). utils.js:35:9
Content Security Policy: The page’s settings blocked the loading of a resource at eval (“script-src”).
Content Security Policy: The page’s settings blocked the loading of a resource at blob:https://localhost-core/4c1fd9a7-44f6-4b2b-a83a-72bda549fda5 (“script-src”).
Content Security Policy: The report URI (about:blank) should be an HTTP or HTTPS URI.
Content Security Policy: The page’s settings observed the loading of a resource at blob:https://localhost-core/4c1fd9a7-44f6-4b2b-a83a-72bda549fda5 (“worker-src”). A CSP report is being sent.

Swagger UI in Firefor:

Content Security Policy: The page’s settings blocked the loading of a resource at eval (“script-src”).
Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”). utils.js:35:9
Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”). onloadwff.js:71:799505
Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”). swaggerui:33:1
Content Security Policy: The page’s settings blocked the loading of a resource at eval (“script-src”).

split(...).join is not a function error with Express v5

I get the following error when running Express v5:

TypeError: split(...).join is not a function
        at ...\node_modules\@wesleytodd\openapi\lib\generate-doc.js:73:47
        at Array.forEach (<anonymous>)
        at iterateStack (...\node_modules\@wesleytodd\openapi\lib\generate-doc.js:71:24)
        at ...\node_modules\@wesleytodd\openapi\lib\generate-doc.js:17:5
        at Array.forEach (<anonymous>)
        at generateDocument (...\node_modules\@wesleytodd\openapi\lib\generate-doc.js:16:26)
        at OpenApiMiddleware (...\node_modules\@wesleytodd\openapi\index.js:41:29)
        at Layer.handleRequest (...\node_modules\express\node_modules\router\lib\layer.js:101:15)
        at trimPrefix (...\node_modules\express\node_modules\router\index.js:330:13)
        at ...\node_modules\express\node_modules\router\index.js:291:7

The split function seems to reach the last "complex" return case, giving a string instead of an array

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.