Skip to content
This repository was archived by the owner on Feb 17, 2021. It is now read-only.

Commit 5148728

Browse files
committedJan 19, 2018
feature/upgrde-all - updated to better file structure and added swagger ui - also added ability to version and additional middleware
1 parent ba46399 commit 5148728

18 files changed

+6218
-1356
lines changed
 

‎.babelrc

+5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
"targets": {
55
"node": "current"
66
},
7+
"useBuiltIns": true,
78
"debug": true
89
}]
10+
],
11+
"plugins": [
12+
"transform-runtime",
13+
"transform-object-rest-spread"
914
]
1015
}

‎.eslintrc.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"parser": "babel-eslint",
3-
"extends": "airbnb-base",
3+
"extends": [
4+
"airbnb-base",
5+
"prettier"
6+
],
47
"env": {
58
"node": true,
69
"mocha": true
@@ -13,7 +16,8 @@
1316
"import/no-extraneous-dependencies": 0,
1417
"global-require": 0,
1518
"radix": 0,
16-
"no-param-reassign": 0
19+
"no-param-reassign": 0,
20+
"no-console": 0
1721
},
1822
"plugins": [
1923
"import"

‎config/default-example.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"server": {
33
"host": "localhost",
4-
"port": 40001
4+
"port": 40001,
5+
"swagger": {
6+
"enable": true
7+
}
58
}
69
}

‎package.json

+6-3
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@
2424
"eslint": "^4.2.0",
2525
"eslint-config-airbnb": "^15.0.2",
2626
"eslint-plugin-import": "^2.7.0",
27-
"eslint-plugin-jsx-a11y": "^6.0.2",
2827
"husky": "^0.14.3",
2928
"lint-staged": "^4.0.1",
3029
"mocha": "^3.4.2",
3130
"morgan": "^1.8.2",
32-
"nodemon": "^1.11.0",
3331
"nyc": "^11.0.3",
3432
"prettier": "^1.5.2",
3533
"prettier-eslint": "^6.4.2",
@@ -39,19 +37,24 @@
3937
"dependencies": {
4038
"babel-cli": "^6.24.1",
4139
"babel-core": "^6.25.0",
40+
"babel-plugin-transform-runtime": "^6.23.0",
4241
"babel-preset-env": "^1.6.0",
4342
"babel-preset-es2015": "^6.24.1",
4443
"babel-preset-stage-0": "^6.24.1",
4544
"body-parser": "^1.17.2",
45+
"chalk": "^2.3.0",
46+
"compose-middleware": "^4.0.0",
4647
"compression": "^1.7.0",
4748
"config": "^1.26.1",
4849
"cookie-parser": "^1.4.3",
4950
"cross-env": "^5.0.1",
51+
"eslint-config-prettier": "^2.9.0",
5052
"express": "^4.15.3",
5153
"helmet": "^3.6.1",
5254
"nodemon": "^1.11.0",
5355
"request": "^2.81.0",
54-
"rimraf": "^2.6.1"
56+
"rimraf": "^2.6.1",
57+
"swagger-ui-express": "^2.0.13"
5558
},
5659
"engines": {
5760
"node": ">=6.10.0",

‎src/index.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ const app = server.create();
66
const { host, port } = config.get('server');
77

88
routes.register(app);
9+
console.log(app._router.stack);
910

1011
app.listen(port, () => {
1112
console.log(`✅ 😀 - Front-End server is running at http://${host}:${port}`);
12-
console.log(`Try me: http://${host}:${port}/v1/someapi/promise`);
13+
console.log(
14+
`Try me: http://${host}:${port}/api/v1/something or http://${host}:${port}/api/v2/something`,
15+
);
1316
});

‎src/middleware/error-handler.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// eslint-disable-next-line no-unused-vars
2+
const errorHandler = (err, req, res, next) => {
3+
const status = err.status || 500;
4+
return res.status(status).send({
5+
status,
6+
error: err.message || 'Something failed!',
7+
});
8+
};
9+
10+
const invalidEndpoint = (req, res, next) => {
11+
const error = new Error(
12+
'Ooops... It looks like this query or endpoint is incorrect/invalid',
13+
);
14+
error.status = 404;
15+
return next(error);
16+
};
17+
18+
export { errorHandler, invalidEndpoint };

‎src/middleware/examples.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const example = (req, res, next) => {
2+
// Example middleware to add a property to all routes
3+
req.example = 'example';
4+
console.log(
5+
`THIS MIDDLEWARE APPLIES TO ALL ROUTES - Example - added global param ${
6+
req.example
7+
}`,
8+
);
9+
return next();
10+
};
11+
12+
const example1 = (req, res, next) => {
13+
console.log(
14+
`THIS MIDDLEWARE APPLIES TO ALL ROUTES - Example1 - Time: ${Date.now()}`,
15+
);
16+
return next();
17+
};
18+
19+
export { example, example1 };

‎src/middleware/index.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
export default {
2-
example(req, res, next) {
3-
// Example middleware to add a property to all routes
4-
req.example = 'example';
5-
return next();
6-
},
7-
};
1+
import { example, example1 } from './examples';
2+
import { errorHandler, invalidEndpoint } from './error-handler';
3+
4+
export { example, example1, errorHandler, invalidEndpoint };

‎src/routes/index.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import express from 'express';
2+
import v1Routes from './v1';
3+
import v2Routes from './v2';
4+
5+
const router = express.Router();
6+
7+
router.use('/v1', v1Routes);
8+
router.use('/v2', v2Routes);
9+
10+
export default router;

‎src/routes/v1/index.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import express from 'express';
2+
3+
const router = express.Router();
4+
5+
router.use((req, res, next) => {
6+
console.log('THIS MIDDLEWARE WILL ONLY APPLY TO THIS ROUTERS ROUTE V1');
7+
return next();
8+
});
9+
10+
router.get('/something', (req, res, next) => {
11+
const error = new Error('An error occurred');
12+
return next(error);
13+
});
14+
15+
export default router;

‎src/routes/v2/index.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import express from 'express';
2+
3+
const router = express.Router();
4+
5+
router.use((req, res, next) => {
6+
console.log('THIS MIDDLEWARE WILL ONLY APPLY TO THIS ROUTERS ROUTE V2');
7+
return next();
8+
});
9+
10+
router.get('/something', (req, res) => res.status(200).send({ success: true }));
11+
12+
export default router;

‎src/service/routes.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import routes from '../routes/example';
1+
import routes from '../routes';
2+
import { errorHandler, invalidEndpoint } from '../middleware';
23

34
export default {
45
register(app) {
56
// Prevent 404 when locating favicon
67
app.get('/favicon.ico', (req, res) => res.sendStatus(204));
7-
app.get('/v1/someapi/async', routes.asyncExample);
8-
app.get('/v1/someapi/promise', routes.promiseExample);
9-
app.post('/v1/someapi/example', routes.asyncExample);
8+
// All routes start with api
9+
// Then split off into versions inside routes
10+
app.use('/api', routes);
11+
// Check for invalid endpoint
12+
app.use(invalidEndpoint);
13+
// Error handler - must be last to pick up errors
14+
app.use(errorHandler);
1015
},
1116
};

‎src/service/server.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,32 @@ import cookieParser from 'cookie-parser';
44
import express from 'express';
55
import helmet from 'helmet';
66
import morgan from 'morgan';
7+
import config from 'config';
8+
import swaggerSetup from './swagger';
79

8-
import middleware from '../middleware';
10+
// Middleware - error handler must go inside routes as this needs
11+
// to be the last app.use and applied after routes is defined
12+
import { example, example1 } from '../middleware';
913

1014
const app = express();
1115

1216
app.use(helmet());
1317
app.use(bodyParser.urlencoded({ extended: true }));
1418
app.use(bodyParser.json());
1519
app.use(cookieParser());
20+
1621
// Middleware applied to all routes
17-
app.use(middleware.example);
22+
app.use(example, example1);
1823
app.use(compression());
1924

2025
// use morgan to log requests to the console
2126
app.use(morgan('dev'));
2227

28+
// If swagger is enabled then load in config etc
29+
if (config.get('server.swagger.enable')) {
30+
app.use('/docs', swaggerSetup(app));
31+
}
32+
2333
export default {
2434
create() {
2535
return app;

‎src/service/swagger.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { serve, setup } from 'swagger-ui-express';
2+
import { compose } from 'compose-middleware';
3+
import fs from 'fs';
4+
import path from 'path';
5+
import chalk from 'chalk';
6+
7+
const retrieveSwaggerDocument = filename => {
8+
const location = path.join(process.cwd(), filename || 'swagger.json');
9+
10+
return new Promise((resolve, reject) =>
11+
fs.readFile(location, (err, data) => {
12+
if (err)
13+
return reject(
14+
`Oooops... It seems we cannot locate a swagger.json file - this should be in the root of your project.`,
15+
);
16+
return resolve(JSON.parse(data));
17+
}),
18+
);
19+
};
20+
21+
function swaggerSetup(app, showExplorer = true, doc = null, options = {}) {
22+
return async (req, res, next) => {
23+
try {
24+
const document = await retrieveSwaggerDocument(doc);
25+
26+
return compose([
27+
serve,
28+
setup(document, showExplorer, {
29+
validatorUrl: null,
30+
docExpansion: 'list',
31+
...options,
32+
}),
33+
])(req, res, next);
34+
} catch (error) {
35+
console.log(chalk.red(error));
36+
return next({
37+
status: 404,
38+
message: error,
39+
});
40+
}
41+
};
42+
}
43+
44+
export default swaggerSetup;

‎swagger.json

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"swagger": "2.0",
3+
"basePath": "/api",
4+
"info": {
5+
"title": "API Boilerplate - example swagger document",
6+
"version": "v2"
7+
},
8+
"paths": {
9+
"/v1/something": {
10+
"get": {
11+
"operationId": "listVersionsv2",
12+
"summary": "Example error on V1 route",
13+
"produces": [
14+
"application/json"
15+
],
16+
"tags": [
17+
"v1"
18+
],
19+
"responses": {
20+
"500": {
21+
"description": "500 response"
22+
}
23+
}
24+
}
25+
},
26+
"/v2/something": {
27+
"get": {
28+
"operationId": "getVersionDetailsv2",
29+
"summary": "Example success on v2 route",
30+
"produces": [
31+
"application/json"
32+
],
33+
"tags": [
34+
"v2"
35+
],
36+
"responses": {
37+
"200": {
38+
"description": "200 response"
39+
}
40+
}
41+
}
42+
}
43+
},
44+
"consumes": [
45+
"application/json"
46+
]
47+
}

‎tests/src/middleware/index.spec.js

Whitespace-only changes.

0 commit comments

Comments
 (0)