1 | <h1 align="center">
|
2 | Node.js Service Tools
|
3 | </h1>
|
4 |
|
5 | <p align="center">
|
6 | <strong>Prepare your Node.js application for production!</strong>
|
7 | </p>
|
8 |
|
9 | <div align="center">
|
10 | <a target="_blank" rel="noopener noreferrer" href="https://circleci.com/gh/banzaicloud/node-service-tools">
|
11 | <img alt="CircleCI" title="CircleCI" src="https://circleci.com/gh/banzaicloud/node-service-tools.svg?style=svg&circle-token=002bb942365281c0834e18a1cb83e1930c3ce0fa" height="20">
|
12 | </a>
|
13 | <a target="_blank" rel="noopener noreferrer" href="https://npmjs.com/package/@banzaicloud/service-tools">
|
14 | <img alt="npm version" title="npm version" src="https://badge.fury.io/js/%40banzaicloud%2Fservice-tools.svg" height="20">
|
15 | </a>
|
16 | </div>
|
17 |
|
18 | This library provides common functionalities, like graceful error handling & shutdown, structured JSON logging and several HTTP middleware to make your application truly ready for modern containerised environments, like [Kubernetes](http://kubernetes.io/).
|
19 |
|
20 |
|
21 |
|
22 | - [Installation](#installation)
|
23 | - [Usage](#usage)
|
24 | - [Main exports](#main-exports)
|
25 | - [`catchErrors(options)`](#catcherrorsoptions)
|
26 | - [`gracefulShutdown(handlers, options)`](#gracefulshutdownhandlers-options)
|
27 | - [`logger`](#logger)
|
28 | - [Use provided logger instead of `console`](#use-provided-logger-instead-of-console)
|
29 | - [Config](#config)
|
30 | - [`config.environment`](#configenvironment)
|
31 | - [`config.pino`](#configpino)
|
32 | - [Middleware (Koa)](#middleware-koa)
|
33 | - [`errorHandler(options)`](#errorhandleroptions)
|
34 | - [`healthCheck(checks, options)`](#healthcheckchecks-options)
|
35 | - [`prometheusMetrics(options)`](#prometheusmetricsoptions)
|
36 | - [`requestValidator(options)`](#requestvalidatoroptions)
|
37 | - [`requestLogger(options)`](#requestloggeroptions)
|
38 | - [Middleware (Express)](#middleware-express)
|
39 | - [`errorHandler(options)`](#errorhandleroptions-1)
|
40 | - [`healthCheck(checks, options)`](#healthcheckchecks-options-1)
|
41 | - [`prometheusMetrics(options)`](#prometheusmetricsoptions-1)
|
42 | - [`requestValidator(options)`](#requestvalidatoroptions-1)
|
43 |
|
44 |
|
45 |
|
46 | ## Installation
|
47 |
|
48 | ```sh
|
49 | npm i @banzaicloud/service-tools
|
50 | # or
|
51 | yarn add @banzaicloud/service-tools
|
52 | ```
|
53 |
|
54 | ## Usage & Examples
|
55 |
|
56 | This library is written in TypeScript, refer to the published types or the source code for argument and return types.
|
57 |
|
58 | Examples are available for [Express](https://expressjs.com/) and [Koa](https://koajs.com/) frameworks. Check out the _[examples](/examples)_ folder!
|
59 |
|
60 | ### Main exports
|
61 |
|
62 | #### `catchErrors(options)`
|
63 |
|
64 | Catch uncaught exceptions and unhandled Promise rejections. It is not safe to resume normal operation after ['uncaughtException'](https://nodejs.org/api/process.html#process_event_uncaughtexception).
|
65 |
|
66 | ```js
|
67 | const { catchErrors } = require('@banzaicloud/service-tools')
|
68 |
|
69 | // ...
|
70 |
|
71 | // the handlers return a Promise
|
72 | // the handlers are called in order
|
73 | catchErrors([closeServer, closeDB])
|
74 |
|
75 | // the error will be caught and the handlers will be called before exiting
|
76 | throw new Error()
|
77 | ```
|
78 |
|
79 | #### `gracefulShutdown(handlers, options)`
|
80 |
|
81 | Graceful shutdown: release resources (databases, HTTP connections, ...) before exiting. When the application receives `SIGTERM` or `SIGINT` signals, the close handlers will be called. The handlers should return a `Promise`.
|
82 |
|
83 | ```js
|
84 | const { gracefulShutdown } = require('@banzaicloud/service-tools')
|
85 |
|
86 | // ...
|
87 |
|
88 | // the handlers return a Promise
|
89 | // the handlers are called in order
|
90 | gracefulShutdown([closeServer, closeDB])
|
91 | ```
|
92 |
|
93 | #### `logger`
|
94 |
|
95 | A [pino](https://github.com/pinojs/pino) structured JSON logger instance configured with [`config.pino`](#config).
|
96 |
|
97 | ```js
|
98 | const { logger } = require('@banzaicloud/service-tools')
|
99 |
|
100 | logger.info({ metadata: true }, 'log message')
|
101 | // > {"level":30,"time":<ts>,"msg":"log message","pid":0,"hostname":"local","metadata":true,"v":1}
|
102 | ```
|
103 |
|
104 | ##### Use provided logger instead of `console`
|
105 |
|
106 | Globally overwrite the `console` and use the logger provided by the library to print out messages.
|
107 |
|
108 | ```js
|
109 | const { logger } = require('@banzaicloud/service-tools')
|
110 |
|
111 | console.log('log message')
|
112 | // > log message
|
113 |
|
114 | logger.interceptConsole()
|
115 |
|
116 | console.log('log message')
|
117 | // > {"level":30,"time":<ts>,"msg":"log message","pid":0,"hostname":"local","v":1}
|
118 | ```
|
119 |
|
120 | ### Config
|
121 |
|
122 | Load configurations dynamically.
|
123 |
|
124 | #### `config.environment`
|
125 |
|
126 | Dynamically load the environment config. It will become available on the `.environment` field. It exports the runtime environment and as a side-effect, it loads `.env` in development. Uses the `NODE_ENV` environment variable, with accepted values of: production, development, test.
|
127 |
|
128 | ```js
|
129 | const { config } = require('@banzaicloud/service-tools')
|
130 | // validates NODE_ENV environment variables when referenced first and load .env when it's "development"
|
131 | console.log(config.environment)
|
132 | // > { nodeEnv: 'production' }
|
133 | ```
|
134 |
|
135 | #### `config.pino`
|
136 |
|
137 | Used by the provided [logger](#logger). Uses the `LOGGER_LEVEL` and `LOGGER_REDACT_FIELDS` environment variables. The `LOGGER_LEVEL` can be one of the following: fatal, error, warn, info, debug, trace. `LOGGER_REDACT_FIELDS` is a comma separated list of field names to mask out in the output (defaults to: `'password, pass, authorization, auth, cookie, _object'`).
|
138 |
|
139 | ```js
|
140 | const pino = require('pino')
|
141 | const { config } = require('@banzaicloud/service-tools')
|
142 |
|
143 | const logger = pino(config.pino)
|
144 |
|
145 | logger.info({ metadata: true, password: 'secret' }, 'log message')
|
146 | // > {"level":30,"time":<ts>,"msg":"log message","pid":0,"hostname":"local","metadata":true,"password":"[REDACTED]","v":1}
|
147 | ```
|
148 |
|
149 | ### Middleware (Koa)
|
150 |
|
151 | Several middleware for the [Koa](https://koajs.com/) web framework.
|
152 |
|
153 | #### `errorHandler(options)`
|
154 |
|
155 | Koa error handler middleware.
|
156 |
|
157 | ```js
|
158 | const Koa = require('koa')
|
159 | const { koa: middleware } = require('@banzaicloud/service-tools').middleware
|
160 |
|
161 | const app = new Koa()
|
162 |
|
163 | // this should be the first middleware
|
164 | app.use(middleware.errorHandler())
|
165 | ```
|
166 |
|
167 | #### `healthCheck(checks, options)`
|
168 |
|
169 | Koa health check endpoint handler.
|
170 |
|
171 | ```js
|
172 | const Koa = require('koa')
|
173 | const Router = require('koa-router')
|
174 | const { koa: middleware } = require('@banzaicloud/service-tools').middleware
|
175 |
|
176 | // ...
|
177 |
|
178 | const app = new Koa()
|
179 | const router = new Router()
|
180 |
|
181 | // the checks return a Promise
|
182 | router.get('/health', middleware.healthCheck([checkDB]))
|
183 |
|
184 | app.use(router.routes())
|
185 | app.use(router.allowedMethods())
|
186 | ```
|
187 |
|
188 | #### `prometheusMetrics(options)`
|
189 |
|
190 | Koa [Prometheus](https://prometheus.io/) metrics endpoint handler. By default it collects some [default metrics](https://github.com/siimon/prom-client#default-metrics).
|
191 |
|
192 | ```js
|
193 | const Koa = require('koa')
|
194 | const Router = require('koa-router')
|
195 | const { koa: middleware } = require('@banzaicloud/service-tools').middleware
|
196 |
|
197 | // ...
|
198 |
|
199 | const app = new Koa()
|
200 | const router = new Router()
|
201 |
|
202 | router.get('/metrics', middleware.prometheusMetrics())
|
203 |
|
204 | app.use(router.routes())
|
205 | app.use(router.allowedMethods())
|
206 | ```
|
207 |
|
208 | #### `requestValidator(options)`
|
209 |
|
210 | Koa request validator middleware. Accepts [Joi](https://github.com/hapijs/joi) schemas for `body` (body parser required), `params` and `query` (query parser required). Returns with `400` if the request is not valid. Assigns validated values to `ctx.state.validated`.
|
211 |
|
212 | ```js
|
213 | const joi = require('joi')
|
214 | const qs = require('qs')
|
215 | const Koa = require('koa')
|
216 | const Router = require('koa-router')
|
217 | const bodyParser = require('koa-bodyparser')
|
218 | const { koa: middleware } = require('@banzaicloud/service-tools').middleware
|
219 |
|
220 | // ...
|
221 |
|
222 | const app = new Koa()
|
223 | const router = new Router()
|
224 |
|
225 | const paramsSchema = joi
|
226 | .object({
|
227 | id: joi
|
228 | .string()
|
229 | .hex()
|
230 | .length(64)
|
231 | .required(),
|
232 | })
|
233 | .required()
|
234 |
|
235 | const bodySchema = joi.object({ name: joi.string().required() }).required()
|
236 |
|
237 | const querySchema = joi.object({ include: joi.array().default([]) }).required()
|
238 |
|
239 | router.get(
|
240 | '/',
|
241 | middleware.requestValidator({ params: paramsSchema, body: bodySchema, query: querySchema }),
|
242 | async function routeHandler(ctx) {
|
243 | const { params, body, query } = ctx.state.validated
|
244 | // ...
|
245 | }
|
246 | )
|
247 |
|
248 | app.use(bodyParser())
|
249 | // query parser
|
250 | app.use(async function parseQuery(ctx, next) {
|
251 | ctx.query = qs.parse(ctx.querystring, options)
|
252 | ctx.request.query = ctx.query
|
253 | await next()
|
254 | })
|
255 | app.use(router.routes())
|
256 | app.use(router.allowedMethods())
|
257 | ```
|
258 |
|
259 | #### `requestLogger(options)`
|
260 |
|
261 | Koa request logger middleware. Useful for local development and debugging.
|
262 |
|
263 | ```js
|
264 | const Koa = require('koa')
|
265 | const { koa: middleware } = require('@banzaicloud/service-tools').middleware
|
266 |
|
267 | // ...
|
268 |
|
269 | const app = new Koa()
|
270 |
|
271 | // this should be the second middleware after the error handler
|
272 | // ...
|
273 | app.use(middleware.requestLogger())
|
274 | ```
|
275 |
|
276 | ### Middleware (Express)
|
277 |
|
278 | Several middleware for the [Express](https://expressjs.com/) web framework.
|
279 |
|
280 | #### `errorHandler(options)`
|
281 |
|
282 | Express error handler middleware.
|
283 |
|
284 | ```js
|
285 | const express = require('express')
|
286 | const { express: middleware } = require('@banzaicloud/service-tools').middleware
|
287 |
|
288 | const app = express()
|
289 |
|
290 | // this should be the last middleware
|
291 | app.use(middleware.errorHandler())
|
292 | ```
|
293 |
|
294 | #### `healthCheck(checks, options)`
|
295 |
|
296 | Express health check endpoint handler.
|
297 |
|
298 | ```js
|
299 | const express = require('express')
|
300 | const { express: middleware } = require('@banzaicloud/service-tools').middleware
|
301 |
|
302 | // ...
|
303 |
|
304 | const app = express()
|
305 |
|
306 | // the checks return a Promise
|
307 | app.get('/health', middleware.healthCheck([checkDB]))
|
308 | ```
|
309 |
|
310 | #### `prometheusMetrics(options)`
|
311 |
|
312 | Express [Prometheus](https://prometheus.io/) metrics endpoint handler. By default it collects some [default metrics](https://github.com/siimon/prom-client#default-metrics).
|
313 |
|
314 | ```js
|
315 | const express = require('express')
|
316 | const { express: middleware } = require('@banzaicloud/service-tools').middleware
|
317 |
|
318 | // ...
|
319 |
|
320 | const app = express()
|
321 |
|
322 | app.get('/metrics', middleware.prometheusMetrics())
|
323 | ```
|
324 |
|
325 | #### `requestValidator(options)`
|
326 |
|
327 | Express request validator middleware. Accepts [Joi](https://github.com/hapijs/joi) schemas for `body` (body parser required), `params` and `query`. Returns with `400` if the request is not valid. Assigns validated values to `req`.
|
328 |
|
329 | ```js
|
330 | const joi = require('joi')
|
331 | const express = require('express')
|
332 | const { express: middleware } = require('@banzaicloud/service-tools').middleware
|
333 |
|
334 | // ...
|
335 |
|
336 | const app = express()
|
337 |
|
338 | const paramsSchema = joi
|
339 | .object({
|
340 | id: joi
|
341 | .string()
|
342 | .hex()
|
343 | .length(64)
|
344 | .required(),
|
345 | })
|
346 | .required()
|
347 |
|
348 | const bodySchema = joi.object({ name: joi.string().required() }).required()
|
349 |
|
350 | const querySchema = joi.object({ include: joi.array().default([]) }).required()
|
351 |
|
352 | app.use(express.json())
|
353 | app.get(
|
354 | '/',
|
355 | middleware.requestValidator({ params: paramsSchema, body: bodySchema, query: querySchema }),
|
356 | function routeHandler(req, res) {
|
357 | const { params, body, query } = req
|
358 | // ...
|
359 | }
|
360 | )
|
361 | ```
|