Skip to content

Commit

Permalink
Openapi 3 support (#32)
Browse files Browse the repository at this point in the history
* initial openapi_3 integration

* update readme

* add support for openapi 3.x

* Update README.md

* add error handler for openapi3

* update deps

* Update error.handler.js
  • Loading branch information
cdimascio authored Apr 4, 2019
1 parent 60cb55b commit 5c5924c
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

![](https://img.shields.io/badge/status-stable-green.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/56c006ccc44c47f49d12b6b35fcf35da)](https://www.codacy.com/app/cdimascio/generator-express-no-stress?utm_source=github.com&utm_medium=referral&utm_content=cdimascio/generator-express-no-stress&utm_campaign=Badge_Grade) ![](https://img.shields.io/badge/license-MIT-blue.svg)

Create awesome [Express.js](http://www.expressjs.com) applications with best of breed tech including ES.next via [Babel.js](https://babeljs.io/), structured logging with [Pino](https://github.com/pinojs/pino), API validation and interactive documentation via [Swagger](http://swagger.io/), environment based config with [dotenv](https://github.com/motdotla/dotenv), and linting with [ESLint](http://eslint.org/).
Create awesome [Express.js](http://www.expressjs.com) applications with best of breed tech including ES.next via [Babel.js](https://babeljs.io/), structured logging with [Pino](https://github.com/pinojs/pino), API validation and interactive documentation using an [OpenAPI 3](https://swagger.io/specification/) or [Swagger 2](http://swagger.io/) spec, environment based config with [dotenv](https://github.com/motdotla/dotenv), and linting with [ESLint](http://eslint.org/).

<p align="center">
<img src="https://raw.githubusercontent.com/cdimascio/generator-express-no-stress/master/assets/express-no-stress-logo-v.png">
Expand Down
49 changes: 40 additions & 9 deletions app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module.exports = class extends Generator {
this.description = 'My cool app';
this.version = '1.0.0';
this.apiRoot = '/api/v1';
this.specification = 'swagger_2';
}

initializing() {}
Expand All @@ -41,6 +42,15 @@ module.exports = class extends Generator {
name: 'apiVersion',
message: `Version [${this.version}]`,
},
{
type: 'list',
name: 'specification',
message: `OpenAPI spec version`,
choices: [
{ name: 'Swagger 2', value: 'swagger_2' },
{ name: 'OpenApi 3 (beta)', value: 'openapi_3' },
],
},
{
type: 'list',
name: 'linter',
Expand All @@ -66,6 +76,7 @@ module.exports = class extends Generator {
this.version = r.version ? r.version : this.version;
this.apiRoot = r.apiRoot ? r.apiRoot.replace(/^\/?/, '/') : this.apiRoot;
this.linter = r.linter;
this.specification = r.specification;
});
}

Expand All @@ -86,20 +97,32 @@ module.exports = class extends Generator {
'.eslintrc.json',
'server/routes.js',
'test/examples.controller.js',
'server/common/swagger/Api.yaml',
'server/common/api.yml',
'server/common/server.js',
'server/api/middlewares/error.handler.js',
'public/api-explorer/index.html',
'public/api-explorer/swagger-ui-standalone-preset.js',
'public/index.html',
'gitignore',
];

const copyOpts = this.docker
? null
: {
globOptions: {
ignore: ['**/+(Dockerfile|.dockerignore)'],
},
};
const copyOpts = {
globOptions: {
ignore: [],
},
};

if (this.specification === 'openapi_3') {
copyOpts.globOptions.ignore.push('**/server/common/swagger.js');
copyOpts.globOptions.ignore.push('**/server/common/api.v2.yml');
} else {
files.push('server/common/api.v2.yml');
copyOpts.globOptions.ignore.push('**/server/common/api.yml');
}
if (this.docker) {
copyOpts.globOptions.ignore.push('**/+(Dockerfile|.dockerignore)');
}

this.fs.copy(src, dest, copyOpts);
this.fs.copy(this.templatePath('.*'), dest, copyOpts);

Expand All @@ -110,20 +133,28 @@ module.exports = class extends Generator {
version: this.version,
apiRoot: this.apiRoot,
linter: this.linter,
specification: this.specification,
};

files.forEach(f => {
this.fs.copyTpl(
this.templatePath(f),
this.destinationPath(`${this.name}/${f}`),
opts
opts,
copyOpts
);
});

this.fs.move(
this.destinationPath(`${this.name}`, 'gitignore'),
this.destinationPath(`${this.name}`, '.gitignore')
);
if (this.specification !== 'openapi_3') {
this.fs.move(
this.destinationPath(`${this.name}`, 'server/common/api.v2.yml'),
this.destinationPath(`${this.name}`, 'server/common/api.yml')
);
}
},
};
}
Expand Down
6 changes: 5 additions & 1 deletion app/templates/.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ LOG_LEVEL=debug
REQUEST_LIMIT=100kb
SESSION_SECRET=mySecret

<% if (specification === 'openapi_3') { %>
OPENAPI_SPEC=<%= apiRoot %>/spec
<% } else { %>
#Swagger
SWAGGER_API_SPEC=/spec
SWAGGER_API_SPEC=/spec
<% } %>
4 changes: 4 additions & 0 deletions app/templates/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
"dotenv": "^6.2.0",
"express": "^4.16.4",
"pino": "^5.11.1",
<% if (specification === 'openapi_3') { %>
"express-openapi-validator": "^0.10.2"
<% } else { %>
"swagger-express-middleware": "^2.0.1"
<% } %>
},
"devDependencies": {
"@babel/cli": "^7.2.3",
Expand Down
16 changes: 16 additions & 0 deletions app/templates/server/api/middlewares/error.handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<% if (specification === 'openapi_3') { %>
// eslint-disable-next-line no-unused-vars, no-shadow
export default function errorHandler(err, req, res, next) {
const errors = err.errors || [{ message: err.message }];
res.status(err.status || 500).json({ errors })
}
<% } else { %>
// Error handler to display the error as HTML
// eslint-disable-next-line no-unused-vars, no-shadow
export default function errorHandler(err, req, res, next) {
res.status(err.status || 500);
res.send(
`<h1>${err.status || 500} Error</h1>` +
`<pre>${err.message}</pre>`);
}
<% } %>
File renamed without changes.
74 changes: 74 additions & 0 deletions app/templates/server/common/api.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
openapi: 3.0.1
info:
title: <%= title %>
description: <%= description %>
version: <%= version %>
servers:
- url: <%= apiRoot %>
tags:
- name: Examples
description: Simple example endpoints
- name: Specification
description: The swagger API specification
paths:
/examples:
get:
tags:
- Examples
description: Fetch all examples
responses:
200:
description: Returns all examples
content: {}
post:
tags:
- Examples
description: Create a new example
requestBody:
description: an example
content:
application/json:
schema:
$ref: '#/components/schemas/ExampleBody'
required: true
responses:
200:
description: Returns all examples
content: {}
/examples/{id}:
get:
tags:
- Examples
parameters:
- name: id
in: path
description: The id of the example to retrieve
required: true
schema:
type: integer
responses:
200:
description: Return the example with the specified id
content: {}
404:
description: Example not found
content: {}
/spec:
get:
tags:
- Specification
responses:
200:
description: Return the API specification
content: {}
components:
schemas:
ExampleBody:
title: example
required:
- name
type: object
properties:
name:
type: string
example: no_stress
20 changes: 19 additions & 1 deletion app/templates/server/common/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import * as bodyParser from 'body-parser';
import * as http from 'http';
import * as os from 'os';
import cookieParser from 'cookie-parser';
<% if (specification === 'openapi_3') { %>
import { OpenApiValidator } from 'express-openapi-validator';
import errorHandler from '../api/middlewares/error.handler';
<% } else { %>
import swaggerify from './swagger';
<% } %>
import l from './logger';

const app = new Express();
Expand All @@ -17,10 +22,23 @@ export default class ExpressServer {
app.use(bodyParser.urlencoded({ extended: true, limit: process.env.REQUEST_LIMIT || '100kb' }));
app.use(cookieParser(process.env.SESSION_SECRET));
app.use(Express.static(`${root}/public`));

<% if (specification === 'openapi_3') { %>
const apiSpecPath = path.join(__dirname, 'api.yml');
app.use(process.env.OPENAPI_SPEC || '/spec', Express.static(apiSpecPath));
new OpenApiValidator({
apiSpecPath,
}).install(app);
<% } %>
}

router(routes) {
swaggerify(app, routes);
<% if (specification === 'openapi_3') { %>
routes(app);
app.use(errorHandler);
<% } else { %>
swaggerify(app, routes);
<% } %>
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import middleware from 'swagger-express-middleware';
import * as path from 'path';
import errorHandler from '../api/middlewares/error.handler';

export default function (app, routes) {
middleware(path.join(__dirname, 'Api.yaml'), app, (err, mw) => {
middleware(path.join(__dirname, 'api.yml'), app, (err, mw) => {
// Enable Express' case-sensitive and strict options
// (so "/entities", "/Entities", and "/Entities/" are all different)
app.enable('case sensitive routing');
Expand Down Expand Up @@ -37,15 +38,9 @@ export default function (app, routes) {
mw.CORS(),
mw.validateRequest());

// Error handler to display the validation error as HTML
// eslint-disable-next-line no-unused-vars, no-shadow
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.send(
`<h1>${err.status || 500} Error</h1>` +
`<pre>${err.message}</pre>`);
});

routes(app);

// eslint-disable-next-line no-unused-vars, no-shadow
app.use(errorHandler);
});
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "generator-express-no-stress",
"version": "6.0.0",
"version": "7.0.1",
"description": "Awesome APIs with ExpressJS and Swagger.",
"main": "app/index.js",
"scripts": {
Expand Down

0 comments on commit 5c5924c

Please sign in to comment.