Skip to content
🦋 Auto-validates api requests and responses using ExpressJS and an OpenAPI 3.x specification
TypeScript JavaScript
Branch: master
Clone or download
Latest commit 522c057 Oct 14, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
assets
example update example Oct 14, 2019
src
test
.all-contributorsrc
.editorconfig update format rules Mar 19, 2019
.gitignore
.npmignore
.nycrc Update .nycrc Sep 25, 2019
.prettierrc.json
.travis.yml update to node 12 Sep 21, 2019
LICENSE
README.md
TODO.md
package-lock.json increment version Oct 14, 2019
package.json
secrets.zip.enc new secrets Jul 15, 2019
tsconfig.json

README.md

express-openapi-validator

Coverage Status All Contributors

An OpenApi validator for ExpressJS that automatically validates API requests and responses using an OpenAPI 3 specification.

express-openapi-validator is unopinionated and does not impose any coding convention or project structure. Simply, install the validator onto your express app, point it to your OpenAPI 3 specification, then define and implement routes the way you prefer. See an example.

Features:

  • ✔️ request validation
  • ✔️ response validation
  • 👮 security validation / custom security functions
  • 👽 3rd party / custom formats
  • 🎈 file upload

GitHub stars Twitter URL

Install

npm i express-openapi-validator

Usage

  1. Install the openapi validator
new OpenApiValidator({
  apiSpec: './test/resources/openapi.yaml',
  validateRequests: true, // (default)
  validateResponses: true, // false by default
}).install(app);
  1. Register an error handler
app.use((err, req, res, next) => {
  // format error
  res.status(err.status || 500).json({
    message: err.message,
    errors: err.errors,
  });
});

Note: Ensure express is configured with all relevant body parsers. body parser middleware functions must be specified prior to any validated routes. See an example

Advanced Usage

For authentication, see securityHandlers in Options.

For OpenAPI 3.0.x 3rd party and custom formats, see Options.

Optionally inline the spec...

The apiSpec option may be specified as the spec object itself, rather than a path e.g.

const apiSpec = {
  openapi: "3.0.1",
  info: {...},
  servers: [...],
  paths: {...},
  components: {
    responses: {...},
    schemas: {...}
  }
}

new OpenApiValidator({ apiSpec }).install(app);

Options

The options object takes the following form.

new OpenApiValidator(options).install({
  apiSpec: './openapi.yaml',
  validateRequests: true,
  validateResponses: true,
  unknownFormats: ['phone-number', 'uuid'],
   multerOpts: { ... },
  securityHandlers: {
    ApiKeyAuth: (req, scopes, schema) => {
      throw { status: 401, message: 'sorry' }
    }
  }
});

Option details:

apiSpec (required)

Specifies the path to an OpenAPI 3 specification or a JSON object representing the OpenAPI 3 specificiation

apiSpec: './path/to/my-openapi-spec.yaml'

or

apiSpec: {
	// the openapi specification as JSON
}

validateRequests (optional)

Determines whether the validator should validate requests.

  • true (default) - validate requests.
  • false - do not validate requests.

validateResponses (optional)

Determines whether the validator should validate responses.

  • true - validate responses
  • false (default) - do not validate responses

unknownFormats (optional)

Defines how the validator should behave if an unknown or custom format is encountered.

  • true (default) - When an unknown format is encountered, the validator will report a 400 error.

  • [string] (recommended for unknown formats) - An array of unknown format names that will be ignored by the validator. This option can be used to allow usage of third party schemas with format(s), but still fail if another unknown format is used.

    e.g.

    unknownFormats: ['phone-number', 'uuid']
  • "ignore" - to log warning during schema compilation and always pass validation. This option is not recommended, as it allows to mistype format name and it won't be validated without any error message.

multerOpts (optional)

Specifies the options to passthrough to multer. express-openapi-validator uses multer to handle file uploads. see multer opts

coerceTypes (optional)

Determines whether the validator should coerce value types to match the type defined in the OpenAPI spec.

  • true (default) - coerce scalar data types.
  • false - no type coercion.
  • "array" - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema).

securityHandlers (optional)

Note: Many use cases do not need securityHandlers. They are most useful for OpenID and OAuth2 scenarios as the securityHandler callback will provide defined scopes and sheme as a convenience.

Specifies a set of custom security handlers to be used to validate security. If a securityHandlers object is specified, a handler must be defined for all securities. If `securityHandlers are not specified, a default handler is always used. The default handler will validate against the OpenAPI spec, then call the next middleware.

If securityHandlers are specified, the validator will validate against the OpenAPI spec, then call the security handler providing it the Express request, the security scopes, and the security schema object.

  • securityHandlers is an object that maps security keys to security handler functions. Each security key must correspond to securityScheme name. The securityHandlers object signature is as follows:

    {
      securityHandlers: {
        [securityKey]: function(
          req: Express.Request,
          scopes: string[],
          schema: SecuritySchemeObject
        ): void,
      }
    }

    SecuritySchemeObject

    For example:

    securityHandlers: {
      ApiKeyAuth: function(req, scopes, schema) {
        console.log('apikey handler throws custom error', scopes, schema);
        throw Error('my message');
      },
    }

    The express-openapi-validator performs a basic validation pass prior to delegating to security handlers. If basic validation passes, security handler function(s) are invoked.

    In order to signal an auth failure, the security handler function must either:

    1. throw { status: 403, message: 'forbidden' }
    2. throw Error('optional message')
    3. return false
    4. return a promise which resolves to false e.g Promise.resolve(false)
    5. return a promise rejection e.g.
      • Promise.reject({ status: 401, message: 'yikes' });
      • Promise.reject(Error('optional 'message')
      • Promise.reject(false)

    Note: error status 401 is returned, unless option i. above is used

    Some examples:

    securityHandlers: {
      ApiKeyAuth: (req, scopes, schema) => {
        throw Error('my message');
      },
      OpenID: async (req, scopes, schema) => {
        throw { status: 403, message: 'forbidden' }
      },
      BasicAuth: (req, scopes, schema) => {
        return Promise.resolve(false);
      },
      ...
    }

    In order to grant authz, the handler function must either:

    • return true
    • return a promise which resolves to true

    Some examples

    securityHandlers: {
      ApiKeyAuth: (req, scopes, schema) => {
        return true;
      },
      BearerAuth: async (req, scopes, schema) => {
        return true;
      },
      ...
    }

    Each securityHandlers securityKey must match a components/securitySchemes property

    components:
      securitySchemes:
        ApiKeyAuth: # <-- Note this name must be used as the name handler function property
          type: apiKey
          in: header
          name: X-API-Key

    See OpenAPI 3 authentication for securityScheme and security documentation

    See examples from unit tests

Example Express API Server

Try the complete example below (source code): (it includes file upload as well!)

const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const logger = require('morgan');
const http = require('http');
const app = express();

// 1. Import the express-openapi-validator library
const OpenApiValidator = require('express-openapi-validator').OpenApiValidator;

// 2. Set up body parsers for the request body types you expect
//    Must be specified prior to endpoints in 4.
app.use(bodyParser.json());
app.use(bodyParser.text());
app.use(bodyParser.urlencoded());

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// 3. (optionally) Serve the OpenAPI spec
app.use('/spec', express.static(spec));

// 4. Install the OpenApiValidator onto your express app
new OpenApiValidator({
  apiSpec: './openapi.yaml',
  // securityHandlers: { ... }, // <-- if using security
  // validateResponses: true, // <-- to validate responses
  // unknownFormats: ['my-format'] // <-- to provide custom formats
}).install(app);

// 4. Define routes using Express
app.get('/v1/pets', function(req, res, next) {
  res.json([{ id: 1, name: 'max' }, { id: 2, name: 'mini' }]);
});

app.post('/v1/pets', function(req, res, next) {
  res.json({ name: 'sparky' });
});

app.get('/v1/pets/:id', function(req, res, next) {
  res.json({ id: req.params.id, name: 'sparky' });
});

// 5. Define route(s) to upload file(s)
app.post('/v1/pets/:id/photos', function(req, res, next) {
  // files are found in req.files
  // non-file multipart params can be found as such: req.body['my-param']

  res.json({
    files_metadata: req.files.map(f => ({
      originalname: f.originalname,
      encoding: f.encoding,
      mimetype: f.mimetype,
      // Buffer of file conents
      buffer: f.buffer,
    })),
  });
});

// 6. Create an Express error handler
app.use((err, req, res, next) => {
  // 7. Customize errors
  res.status(err.status || 500).json({
    message: err.message,
    errors: err.errors,
  });
});

The Base URL

The validator will only validate requests — and (optionally) responses — that are under the server's base URL.

This is useful for those times when the API and frontend are being served by the same application. (More detail about the base URL.)

servers:
  - url: https://api.example.com/v1

The validation applies to all paths defined under this base URL. Routes in your app that are not under the base URL—such as pages—will not be validated.

URL Validated?
https://api.example.com/v1/users
https://api.example.com/index.html no; not under the base URL

Example Express API Server

A fully working example lives here

Example validation responses

Validate a query parameter with a value constraint

curl -s http://localhost:3000/v1/pets/as |jq
{
  "message": "request.params.id should be integer",
  "errors": [
    {
      "path": ".params.id",
      "message": "should be integer",
      "errorCode": "type.openapi.validation"
    }
  ]
}

Validate a query parameter with a range constraint

 curl -s 'http://localhost:3000/v1/pets?limit=25' |jq
{
  "message": "request.query should have required property 'type', request.query.limit should be <= 20",
  "errors": [
    {
      "path": ".query.type",
      "message": "should have required property 'type'",
      "errorCode": "required.openapi.validation"
    },
    {
      "path": ".query.limit",
      "message": "should be <= 20",
      "errorCode": "maximum.openapi.validation"
    }
  ]
}

Validate security

 curl -s --request POST \
  --url http://localhost:3000/v1/pets \
  --data '{}' |jq
{
  "message": "'X-API-Key' header required",
  "errors": [
    {
      "path": "/v1/pets",
      "message": "'X-API-Key' header required"
    }
  ]
}

with api key header

curl -XPOST http://localhost:3000/v1/pets \
  --header 'X-Api-Key: XXXXX' \
  --header 'content-type: application/json' \
  -d '{"name": "spot"}' | jq

{
  "id": 4,
  "name": "spot"
}

Validate content-type

curl -s --request POST \
  --url http://localhost:3000/v1/pets \
  --header 'content-type: application/xml' \
  --header 'x-api-key: XXXX' \
  --data '{
        "name": "test"
}' |jq
  "message": "unsupported media type application/xml",
  "errors": [
    {
      "path": "/v1/pets",
      "message": "unsupported media type application/xml"
    }
  ]
}

Validate a POST request body

curl -s --request POST \
  --url http://localhost:3000/v1/pets \
  --header 'content-type: application/json' \
  --header 'x-api-key: XXXX' \
  --data '{}'|jq
{
  "message": "request.body should have required property 'name'",
  "errors": [
    {
      "path": ".body.name",
      "message": "should have required property 'name'",
      "errorCode": "required.openapi.validation"
    }
  ]
}

File upload example

curl -XPOST http://localhost:3000/v1/pets/10/photos -F file=@app.js|jq
{
  "files_metadata": [
    {
      "originalname": "app.js",
      "encoding": "7bit",
      "mimetype": "application/octet-stream"
    }
  ]
}

Response validation (optional)

Response validation errors return 500s, instead of 400s

/v1/pets/99 will return a response that does not match the spec

 curl -s 'http://localhost:3000/v1/pets/99' |jq
{
  "message": ".response should have required property 'name', .response should have required property 'id'",
  "errors": [
    {
      "path": ".response.name",
      "message": "should have required property 'name'",
      "errorCode": "required.openapi.validation"
    },
    {
      "path": ".response.id",
      "message": "should have required property 'id'",
      "errorCode": "required.openapi.validation"
    }
  ]
}

...and much more. Try it out!

Contributors

Thanks goes to these wonderful people (emoji key):

Carmine DiMascio
Carmine DiMascio

💻 ⚠️ 🚇
Sheldhur Mornor
Sheldhur Mornor

💻 ⚠️
Andrey Trebler
Andrey Trebler

💻 ⚠️

This project follows the all-contributors specification. Contributions of any kind welcome!

License

MIT

Buy Me A Coffee

You can’t perform that action at this time.