merest-swagger

The merest-swagger is an extention of the merest that adds three methods to merest.ModelAPIExpress to provide swagger api documentation support:

  • swaggerDoc() - it returns javascript object that contains full api description in swagger format
  • exposeSwagger() - the method makes the swagger document is accessible by url
  • exposeSwaggerUi() - this method allows you to embed the swagger-ui into your project

Currently only the swagger 2.0 is supported

Example

'use strict';

const merest = require('merest-swagger'); // to support SwaggerUI
const express = require('express');
const bodyParser = require('body-parser');
const methodOverride = require('method-override');

// Defining model
const mongoose = require('mongoose');
const Contact = mongoose.model('Contact', new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true },
  phone: String,
  tags: [String]
}));
mongoose.connect('mongodb://localhost/merest-sample');

const app = express();
// Creating the Express application to serve API
const api = new merest.ModelAPIExpress({
  title: 'Contact List API',
  host: 'localhost:8000', // Assign correct host that could be accessed from your network
  path: '/api/v1',
  options: false // we do not need the OPTIONS any more
});

app.use(bodyParser.json()); // Parsing JSON-bodies
app.use(methodOverride()); // Supporting HTTP OPTIONS and HTTP DELETE methods

api.expose(Contact); // Exposing our API

let swaggerDoc = api.swagerDoc(); // creating a swagger document
api.exposeSwagger('/api-docs', { // mounting the swagger document to the api with path /api-docs
  beautify: true
});
api.exposeSwaggerUi('/api-ui'); // mounting the swagger-ui to the api with path /api-ui

app.use('/api/v1', api); // mounting API as usual sub-application

app.listen(8000, () => {
  console.log('Express server listening on port 8000');
});

Going to swagger-ui in browser: http://localhost:8000/api-ui/

swagger-ui

You could copy the path of swagger document http://localhost:8000/api-docs/ and paste it to the adress line of your browser:

{
  "swagger": "2.0",
  "info": {
    "title": "Contact List API",
    "version": "1.0"
  },
  "host": "localhost:8000",
  "basePath": "/api/v1",
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "tags": [
    {
      "name": "_meta",
      "description": "API description"
    },
    {
      "name": "Contact",
      "description": "Methods to manage Contact"
    }
  ],
  "responses": {
    "405": {
      "description": "The end-point is not supported",
      "schema": {
        "$ref": "#/definitions/modelAPIError_E4xx"
      }
    },
    "422": {
      "description": "Entity validation failed",
      "schema": {
        "$ref": "#/definitions/modelAPIError_E422"
      }
    },
    "500": {
      "description": "Internal API error",
      "schema": {
        "$ref": "#/definitions/modelAPIError_E500"
      }
    }
  },
  "paths": {
    "/contacts/": {
      "get": {
        "tags": [
          "Contact"
        ],
        "operationId": "searchFor_contacts",
        "summary": "Search for contacts",
        "description": "List/Search all contacts",
        "parameters": [
          {
            "name": "_sort",
            "in": "query",
            "type": "string",
            "description": "The list of fields to order by: [-]field[,[-]field]"
          },
          {
            "name": "_limit",
            "in": "query",
            "type": "integer",
            "description": "The maximum number of documents in the response"
          },
          {
            "name": "_skip",
            "in": "query",
            "type": "integer",
            "description": "Number of documents should be skipped in the selection before responding"
          },
          {
            "name": "name",
            "in": "query",
            "type": "string"
          },
          {
            "name": "email",
            "in": "query",
            "type": "string"
          },
          {
            "name": "phone",
            "in": "query",
            "type": "string"
          },
          {
            "name": "tags",
            "in": "query",
            "type": "string"
          },
          {
            "name": "_id",
            "in": "query",
            "type": "string"
          },
          {
            "name": "__v",
            "in": "query",
            "type": "number"
          }
        ],
        "responses": {
          "200": {
            "description": "Successfully searched",
            "schema": {
              "title": "List of contacts",
              "type": "array",
              "items": {
                "$ref": "#/definitions/Contact_Response"
              }
            }
          },
          "405": {
            "$ref": "#/responses/405"
          },
          "500": {
            "$ref": "#/responses/500"
          }
        }
      },
      "post": {
        "tags": [
          "Contact"
        ],
        "operationId": "create_Contact",
        "summary": "Create Contact",
        "description": "Create a new Contact",
        "parameters": [
          {
            "name": "Contact",
            "in": "body",
            "schema": {
              "$ref": "#/definitions/Contact"
            },
            "required": true
          }
        ],
        "responses": {
          "201": {
            "description": "Successfully created",
            "schema": {
              "$ref": "#/definitions/Contact_Response"
            }
          },
          "405": {
            "$ref": "#/responses/405"
          },
          "406": {
            "description": "Wrong method usage (use `post ~/:id` to update an object)",
            "schema": {
              "$ref": "#/definitions/modelAPIError_E4xx"
            }
          },
          "422": {
            "$ref": "#/responses/422"
          },
          "500": {
            "$ref": "#/responses/500"
          }
        }
      }
    },
    "/contacts/{id}": {
      "get": {
        "tags": [
          "Contact"
        ],
        "operationId": "detailsOf_Contact",
        "summary": "Details of Contact",
        "description": "Find a Contact by Id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "type": "string",
            "pattern": "[a-f\\d]{24}",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "The Contact was found successfully.",
            "schema": {
              "$ref": "#/definitions/Contact_Response"
            }
          },
          "404": {
            "description": "Contact is not found by specified id",
            "schema": {
              "$ref": "#/definitions/modelAPIError_E4xx"
            }
          },
          "405": {
            "$ref": "#/responses/405"
          },
          "500": {
            "$ref": "#/responses/500"
          }
        }
      },
      "post": {
        "tags": [
          "Contact"
        ],
        "operationId": "update_Contact",
        "summary": "Update Contact",
        "description": "Find a Contact by Id and update it (particulary)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "type": "string",
            "required": true
          },
          {
            "name": "Contact",
            "in": "body",
            "schema": {
              "$ref": "#/definitions/Contact_Update"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successfully updated",
            "schema": {
              "$ref": "#/definitions/Contact_Response"
            }
          },
          "404": {
            "description": "Contact is not found by specified id",
            "schema": {
              "$ref": "#/definitions/modelAPIError_E4xx"
            }
          },
          "405": {
            "$ref": "#/responses/405"
          },
          "422": {
            "$ref": "#/responses/422"
          },
          "500": {
            "$ref": "#/responses/500"
          }
        }
      },
      "delete": {
        "tags": [
          "Contact"
        ],
        "operationId": "delete_Contact",
        "summary": "Delete Contact",
        "description": "Find a Contact by Id and delete it.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "type": "string",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Successfully deleted",
            "schema": {
              "$ref": "#/definitions/deleteResponse"
            }
          },
          "404": {
            "description": "Contact is not found by specified id",
            "schema": {
              "$ref": "#/definitions/modelAPIError_E4xx"
            }
          },
          "405": {
            "$ref": "#/responses/405"
          },
          "500": {
            "$ref": "#/responses/500"
          }
        }
      }
    }
  },
  "definitions": {
    "modelAPIError_E4xx": {
      "title": "ModelAPIError",
      "type": "object",
      "properties": {
        "error": {
          "type": "boolean"
        },
        "code": {
          "type": "string"
        },
        "message": {
          "type": "string"
        }
      }
    },
    "modelAPIError_E422": {
      "title": "EntityValidationError",
      "type": "object",
      "properties": {
        "error": {
          "type": "boolean"
        },
        "code": {
          "type": "string"
        },
        "message": {
          "type": "string"
        },
        "errors": {}
      }
    },
    "modelAPIError_E500": {
      "title": "InternalError",
      "type": "object",
      "properties": {
        "error": {
          "type": "boolean"
        },
        "message": {
          "type": "string"
        },
        "stack": {}
      }
    },
    "deleteResponse": {
      "type": "object",
      "additionalProperties": false,
      "properties": {}
    },
    "Contact_Response": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "email": {
          "type": "string"
        },
        "phone": {
          "type": "string"
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "_id": {
          "type": "string",
          "format": "uuid",
          "pattern": "^[0-9a-fA-F]{24}$"
        },
        "__v": {
          "type": "number"
        }
      }
    },
    "Contact": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "email": {
          "type": "string"
        },
        "phone": {
          "type": "string"
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "_id": {
          "type": "string",
          "format": "uuid",
          "pattern": "^[0-9a-fA-F]{24}$"
        },
        "__v": {
          "type": "number"
        }
      },
      "required": [
        "name",
        "email"
      ]
    },
    "Contact_Update": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "email": {
          "type": "string"
        },
        "phone": {
          "type": "string"
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "_id": {
          "type": "string",
          "format": "uuid",
          "pattern": "^[0-9a-fA-F]{24}$"
        }
      }
    }
  }
}

After that you could paste the link into swagger-editor and work with your api documentation with that tool, generating preffered client or server project templates.


Installation

npm up merest
npm i --save merest-swagger

After merest-swagger will be installed you could import it into your project instead of merest.

'use strict;'
const merest = require('merest-swagger'); // insted of: require('merest');
...

However merest-swagger requires merest to be also installed but it doesn't install the merest directly. It is crutial to add support into you project correctly.

Recommended Installation

npm i --save merest merest-swagger mongoose express body-parser method-override

Development

git clone https://github.com/DScheglov/merest-swagger.git
cd merest-swagger
npm install
npm run-script test-loc

API Configuration

In order to provide correct swagger documentation you should assign API Application parameters correctly:

  • title: String - The title of API.
  • version: String - The version of API.
  • path: String - The path of api root. The application doesn't mount it self on this path.
  • host: String - The host to reach API.

You could do it during the ModelAPIExpress construction:

const api = new merest.ModelAPIExpress({
  title: 'Cool API',
  version: '0.0.1',
  path: '/api/v1',
  host: 'mydomain.com'
})

Also you could to pass the same parameters to the swagger-support methods (swaggerDoc(), exposeSwagger(), exposeSwaggerUi()). Please, consider that the parameters could be aplied only one time, so the subsequent assignment will have no effect.

Creating swagger.json

In order to create swagger documentation, just call swaggerDoc() method after all models will be exposed:

...
api.expose(lastModel);
console.dir(api.swaggerDoc(), { depth: null });

The swagger document will be created only one time. All subsiquent calls of swaggerDoc() will return the same document. If in some reason you need to avoid this behavior, just rest the __swaggerDoc field of ModelAPIExpress instance:

  ...
  let swaggerDoc = api.swaggerDoc();
  ...
  api.expose(oneMoreModel);
  api.__swaggerDoc = null;

  swaggerDoc = api.swaggerDoc(); // this call will already consider the oneModeModel exposed
  ...

If you don't assign the API configuration parameters before swaggerDoc method call you could path them into this call:

...
  api.expose(lastModel);
  let swaggerDoc = api.swaggerDoc({
    title: 'Cool API',
    version: '0.0.1',
    path: '/api/v1',
    host: 'mydomain.com'
  });

Exposing the api-docs

The merest-swagger allows to expose the swagger document via your api. To do so just call the exposeSwagger() method of ModelAPIExpress instance:

...
const api = merest.ModelAPIExpress({
  title: 'Cool API',
  version: '0.0.1',
  path: '/api/v1',
  host: 'mydomain.com'
});
api.expose(aModel);
api.exposeSwagger('/api-docs', {
  beautify: true,
  cors: true
});

The method accepts two parameters (the both are optional):

  • path - the path to mount the swagger document ('/swagger.json' by default)
  • options - the swagger document exposition options
    • options.beautify: String | Bolean -- the same-named parameter of JSON.stringify()
    • options.cors: Bolean -- the flag indicated swagger document should be exposed with CORS headers (the default value is true)

If you don't assign the API configuration parameters before exposeSwagger method call you could path them into this call

Embeding swagger-ui

The most showy way to touch the api is to use the swagger-ui.

You could follow the instructions of the swagger-api project: clone repository setup some server and use the created by the merest-swagger api documentation in swagger format.

But the merest-swagger allows you to embed this Swagger User interface in easest way by calling method exposeSwaggerUi() of ModelAPIExpress instance:

...
const api = merest.ModelAPIExpress({
  title: 'Cool API',
  version: '0.0.1',
  path: '/api/v1',
  host: 'mydomain.com'
});
api.expose(aModel);
api.exposeSwaggerUi({
  swaggerDoc: '/api-docs'
});

The method accepts two parameters (the both are optional):

  • path - the path to mount the swagger-ui ('/swagger-ui' by default)
  • options - the swagger-ui exposition options
    • options.swaggerDoc: String -- the path to mount swagger document if it is not mounted yet

The options parameter could contain keys for swagger document exposition, such as beautify or cors.

Also if you don't assign the API configuration parameters before exposeSwaggerUi method call you could path them into this call



Next (Installation) >