@apolitical/server 2.5.0-rc.1 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,9 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.5.1] - 2022-06-01
9
+ ### Added
10
+ - Cache to `readinessCheck` and `livenessCheck` checks to avoid overload
11
+
8
12
  ## [2.5.0] - 2022-05-06
9
13
  ### Added
10
- - Custom `livenessCheck` to define liveness externally
14
+ - Custom `readinessCheck` and `livenessCheck` to define probes externally
15
+ - New probe `/health` endpoint to combine readiness and liveness
11
16
 
12
17
  ## [2.4.2] - 2022-03-03
13
18
  ### Bumped
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apolitical/server",
3
- "version": "2.5.0-rc.1",
3
+ "version": "2.5.1",
4
4
  "description": "Node.js module to encapsulate Apolitical's express server setup",
5
5
  "author": "Apolitical Group Limited <engineering@apolitical.co>",
6
6
  "license": "MIT",
@@ -31,28 +31,29 @@
31
31
  "compression": "1.7.4",
32
32
  "cookie-parser": "1.4.6",
33
33
  "cors": "2.8.5",
34
- "dotenv": "16.0.0",
34
+ "dotenv": "16.0.1",
35
35
  "express": "4.18.1",
36
- "express-jwt": "7.6.2",
36
+ "express-jwt": "7.7.5",
37
37
  "http-status-codes": "2.2.0",
38
38
  "http-terminator": "3.2.0",
39
- "jsrsasign": "10.5.20",
40
- "jwks-rsa": "2.1.1",
39
+ "jsrsasign": "10.5.23",
40
+ "jwks-rsa": "2.1.3",
41
41
  "jwt-decode": "3.1.2",
42
+ "lru-cache": "7.10.1",
42
43
  "morgan": "1.10.0",
43
- "passport": "0.5.2",
44
+ "passport": "0.6.0",
44
45
  "passport-jwt": "4.0.0",
45
46
  "prerender-node": "3.5.0",
46
- "swagger-ui-express": "4.3.0"
47
+ "swagger-ui-express": "4.4.0"
47
48
  },
48
49
  "devDependencies": {
49
- "@apolitical/eslint-config": "2.0.0",
50
+ "@apolitical/eslint-config": "2.0.1",
50
51
  "@apolitical/testing": "1.0.2",
51
- "audit-ci": "6.2.0",
52
- "husky": "7.0.4",
52
+ "audit-ci": "6.3.0",
53
+ "husky": "8.0.1",
53
54
  "jest": "28.1.0",
54
55
  "jest-junit": "13.2.0",
55
- "lint-staged": "12.4.1",
56
+ "lint-staged": "13.0.0",
56
57
  "mock-jwks": "1.0.3",
57
58
  "nock": "13.2.4"
58
59
  },
package/src/config.js CHANGED
@@ -18,6 +18,7 @@ module.exports = {
18
18
  },
19
19
  ENDPOINTS: {
20
20
  PROBES: {
21
+ HEALTH: '/health',
21
22
  LIVENESS: '/liveness',
22
23
  READINESS: '/readiness',
23
24
  },
@@ -48,6 +49,10 @@ module.exports = {
48
49
  USER_SLUG: 'user-slug',
49
50
  },
50
51
  },
52
+ CACHE_OPTIONS: {
53
+ max: 2, // Only liveness and readiness
54
+ ttl: 60 * 1000, // One minute
55
+ },
51
56
  },
52
57
  JWT: {
53
58
  APOLITICAL: {
package/src/container.js CHANGED
@@ -14,6 +14,7 @@ const httpTerminator = require('http-terminator');
14
14
  const jwksRsa = require('jwks-rsa');
15
15
  const jsrsasign = require('jsrsasign');
16
16
  const jwtDecode = require('jwt-decode');
17
+ const lru = require('lru-cache');
17
18
  const morgan = require('morgan');
18
19
  const passport = require('passport');
19
20
  const passportJwt = require('passport-jwt');
@@ -34,6 +35,7 @@ const jwtPassportHelper = require('./helpers/jwt/passport.helper');
34
35
  const loggerHelper = require('./helpers/logger.helper');
35
36
  // Loaders
36
37
  const documentationLoader = require('./loaders/documentation.loader');
38
+ const expressLoader = require('./loaders/express.loader');
37
39
  const loggerLoader = require('./loaders/logger.loader');
38
40
  const middlewaresLoader = require('./loaders/middlewares.loader');
39
41
  const probesLoader = require('./loaders/probes.loader');
@@ -45,6 +47,8 @@ const jwtApoliticalMiddleware = require('./middlewares/jwt/apolitical.middleware
45
47
  const jwtAuth0Middleware = require('./middlewares/jwt/auth0.middleware');
46
48
  const errorMiddleware = require('./middlewares/error.middleware');
47
49
  // Services
50
+ const expressService = require('./services/express.service');
51
+ const healthService = require('./services/health.service');
48
52
  const jwtService = require('./services/jwt.service');
49
53
  const serverService = require('./services/server.service');
50
54
 
@@ -64,6 +68,7 @@ container.register({
64
68
  jwksRsa: asValue(jwksRsa),
65
69
  jsrsasign: asValue(jsrsasign),
66
70
  jwtDecode: asValue(jwtDecode),
71
+ lru: asValue(lru),
67
72
  morgan: asValue(morgan),
68
73
  passport: asValue(passport),
69
74
  passportJwt: asValue(passportJwt),
@@ -84,6 +89,7 @@ container.register({
84
89
  loggerHelper: asFunction(loggerHelper).singleton(),
85
90
  // Loaders
86
91
  documentationLoader: asFunction(documentationLoader).singleton(),
92
+ expressLoader: asFunction(expressLoader).singleton(),
87
93
  loggerLoader: asFunction(loggerLoader).singleton(),
88
94
  middlewaresLoader: asFunction(middlewaresLoader).singleton(),
89
95
  probesLoader: asFunction(probesLoader).singleton(),
@@ -95,6 +101,8 @@ container.register({
95
101
  jwtAuth0Middleware: asFunction(jwtAuth0Middleware).singleton(),
96
102
  errorMiddleware: asFunction(errorMiddleware).singleton(),
97
103
  // Services
104
+ expressService: asFunction(expressService).singleton(),
105
+ healthService: asFunction(healthService).singleton(),
98
106
  jwtService: asFunction(jwtService).singleton(),
99
107
  serverService: asFunction(serverService).singleton(),
100
108
  });
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ module.exports = ({
4
+ logger,
5
+ documentationLoader,
6
+ loggerLoader,
7
+ errorMiddleware,
8
+ middlewaresLoader,
9
+ probesLoader,
10
+ fallbackLoader,
11
+ }) => {
12
+ // Main (express) loader
13
+ return async function load(app, opts) {
14
+ const childLogger = logger.where(__filename, 'load');
15
+ childLogger.debug('Started');
16
+ // Load probes
17
+ probesLoader(app, opts);
18
+ // Load useful middlewares
19
+ middlewaresLoader(app, opts);
20
+ // Load logger
21
+ await loggerLoader(app, opts);
22
+ // Load custom resources
23
+ if (opts.appLoader) {
24
+ await opts.appLoader(app);
25
+ }
26
+ // Load documentation
27
+ documentationLoader(app, opts);
28
+ // Load fallback routing
29
+ fallbackLoader(app, opts);
30
+ // Use error handler
31
+ if (opts.handleErrors) {
32
+ app.use(errorMiddleware(opts));
33
+ }
34
+ childLogger.debug('Finished');
35
+ };
36
+ };
@@ -1,20 +1,22 @@
1
1
  'use strict';
2
2
 
3
- module.exports = ({ health, config, logger }) => {
4
- const { LIVENESS, READINESS } = config.ENDPOINTS.PROBES;
3
+ module.exports = ({ config, logger, healthService }) => {
4
+ const { HEALTH, LIVENESS, READINESS } = config.ENDPOINTS.PROBES;
5
5
 
6
- return function load(app, { livenessCheck }) {
6
+ return function load(app, { readinessCheck, livenessCheck }) {
7
7
  const childLogger = logger.where(__filename, 'load');
8
8
  childLogger.debug('Started');
9
- // Setup health checker
10
- const healthCheck = new health.HealthChecker();
11
- // Overwrite live check when it is defined
9
+ // Overwrite checks when defined
10
+ if (readinessCheck) {
11
+ healthService.registerReadiness(readinessCheck);
12
+ }
12
13
  if (livenessCheck) {
13
- healthCheck.registerLivenessCheck(new health.LivenessCheck('livenessCheck', livenessCheck));
14
+ healthService.registerLiveness(livenessCheck);
14
15
  }
15
16
  // Load probes endpoints
16
- app.get(LIVENESS, health.LivenessEndpoint(healthCheck));
17
- app.get(READINESS, health.ReadinessEndpoint(healthCheck));
17
+ app.get(HEALTH, healthService.healthEndpoint());
18
+ app.get(READINESS, healthService.readinessEndpoint());
19
+ app.get(LIVENESS, healthService.livenessEndpoint());
18
20
  childLogger.debug('Finished');
19
21
  };
20
22
  };
@@ -2,7 +2,7 @@
2
2
 
3
3
  module.exports = ({ expressJwt: { expressjwt: jwt }, jwksRsa, config }) => {
4
4
  const { CACHE, RATE_LIMIT, RPM, URI, ALGORITHMS } = config.JWT.AUTH0;
5
- // Express JWT authentication (Auth0)
5
+ // Define express JWT authentication (Auth0)
6
6
  return function handler({ domain, audience, issuer }) {
7
7
  return jwt({
8
8
  secret: jwksRsa.expressJwtSecret({
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ module.exports = ({ express, httpTerminator, logger, expressLoader }) => {
4
+ let app = null;
5
+ let terminator = null; // I need your clothes, your boots and your motorcycle
6
+
7
+ function expressRunner(app, port) {
8
+ const childLogger = logger.where(__filename, 'expressRunner');
9
+ childLogger.debug('Started');
10
+ return new Promise((resolve, reject) => {
11
+ if (!port) {
12
+ childLogger.warn('Missing port option');
13
+ return reject(new Error('Cannot start server without port option'));
14
+ }
15
+ const server = app.listen(port, () => {
16
+ childLogger.info(`Server listening on ${port}`);
17
+ return resolve();
18
+ });
19
+ terminator = httpTerminator.createHttpTerminator({ server });
20
+ childLogger.debug('Finished');
21
+ });
22
+ }
23
+
24
+ async function startup(opts) {
25
+ const childLogger = logger.where(__filename, 'startup');
26
+ childLogger.debug('Started');
27
+ // Create, load and run the express app
28
+ if (!app) {
29
+ app = express();
30
+ await expressLoader(app, opts);
31
+ await expressRunner(app, opts.port);
32
+ }
33
+ childLogger.debug('Finished');
34
+ return app;
35
+ }
36
+
37
+ async function shutdown() {
38
+ const childLogger = logger.where(__filename, 'shutdown');
39
+ childLogger.debug('Started');
40
+ // Terminate process and clean-up variables
41
+ await terminator.terminate();
42
+ app = null;
43
+ terminator = null;
44
+ childLogger.debug('Finished');
45
+ }
46
+
47
+ return { startup, shutdown };
48
+ };
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ module.exports = ({ health, lru, config }) => {
4
+ const { CACHE_OPTIONS } = config.SERVER;
5
+ // Setup health checker
6
+ const healthCheck = new health.HealthChecker();
7
+ // Setup cache
8
+ const cache = new lru(CACHE_OPTIONS);
9
+
10
+ function buildCheckWithCache(key, check) {
11
+ cache.clear();
12
+ return async function () {
13
+ const checked = cache.get(key);
14
+ if (!checked) {
15
+ await check();
16
+ cache.set(key, true);
17
+ }
18
+ };
19
+ }
20
+
21
+ // function registerStartup(startup) {
22
+ // healthCheck.registerStartupCheck(new health.StartupCheck("Startup Check", startup));
23
+ // }
24
+
25
+ function healthEndpoint() {
26
+ return health.HealthEndpoint(healthCheck);
27
+ }
28
+
29
+ function registerReadiness(readiness) {
30
+ const readinessWithCache = buildCheckWithCache('readiness', readiness);
31
+ healthCheck.registerReadinessCheck(new health.ReadinessCheck('Readiness Check', readinessWithCache));
32
+ }
33
+
34
+ function readinessEndpoint() {
35
+ return health.ReadinessEndpoint(healthCheck);
36
+ }
37
+
38
+ function registerLiveness(liveness) {
39
+ const livenessWithCache = buildCheckWithCache('liveness', liveness);
40
+ healthCheck.registerLivenessCheck(new health.LivenessCheck('Liveness Check', livenessWithCache));
41
+ }
42
+
43
+ function livenessEndpoint() {
44
+ return health.LivenessEndpoint(healthCheck);
45
+ }
46
+
47
+ function registerShutdown(shutdown) {
48
+ healthCheck.registerShutdownCheck(new health.ShutdownCheck('Shutdown Check', shutdown));
49
+ }
50
+
51
+ return { healthEndpoint, readinessEndpoint, registerReadiness, registerLiveness, livenessEndpoint, registerShutdown };
52
+ };
@@ -1,75 +1,19 @@
1
1
  'use strict';
2
2
 
3
3
  module.exports = ({
4
- express,
5
- httpTerminator,
6
- logger,
7
- documentationLoader,
8
- loggerLoader,
9
4
  authenticationMiddleware,
10
5
  authorisationMiddleware,
11
- errorMiddleware,
12
- middlewaresLoader,
13
- probesLoader,
14
- fallbackLoader,
6
+ expressService: { startup, shutdown },
7
+ healthService: { registerShutdown },
15
8
  jwtService,
16
9
  serverError,
17
10
  }) => {
18
- let terminator = null; // I need your clothes, your boots and your motorcycle.
19
-
20
- function run(app, port) {
21
- const childLogger = logger.where(__filename, 'run');
22
- childLogger.debug('Started');
23
- return new Promise((resolve, reject) => {
24
- if (!port) {
25
- childLogger.warn('Missing port option');
26
- return reject(new Error('Cannot start server without port option'));
27
- }
28
- const server = app.listen(port, () => {
29
- childLogger.info(`Server listening on ${port}`);
30
- return resolve();
31
- });
32
- terminator = httpTerminator.createHttpTerminator({ server });
33
- childLogger.debug('Finished');
34
- });
35
- }
11
+ // Register shutdown to clean up any resources used by the express app
12
+ registerShutdown(shutdown);
36
13
 
37
14
  return {
38
- async start(opts) {
39
- const childLogger = logger.where(__filename, 'start');
40
- childLogger.debug('Started');
41
- // Create Express server
42
- const app = express();
43
- // Load probes
44
- probesLoader(app, opts);
45
- // Load useful middlewares
46
- middlewaresLoader(app, opts);
47
- // Load logger
48
- await loggerLoader(app, opts);
49
- // Load custom resources
50
- if (opts.appLoader) {
51
- await opts.appLoader(app);
52
- }
53
- // Load documentation
54
- documentationLoader(app, opts);
55
- // Load fallback routing
56
- fallbackLoader(app, opts);
57
- // Use error handler
58
- if (opts.handleErrors) {
59
- app.use(errorMiddleware(opts));
60
- }
61
- // Run the server
62
- await run(app, opts.port);
63
- childLogger.debug('Finished');
64
- return app;
65
- },
66
- async stop() {
67
- const childLogger = logger.where(__filename, 'stop');
68
- childLogger.debug('Started');
69
- // Terminate server process
70
- await terminator.terminate();
71
- childLogger.debug('Finished');
72
- },
15
+ start: startup,
16
+ stop: shutdown,
73
17
  jwt: jwtService,
74
18
  errors: serverError,
75
19
  middlewares: {