@gquittet/graceful-server
Advanced tools
Comparing version 5.2.3 to 5.2.4
{ | ||
"name": "@gquittet/graceful-server", | ||
"version": "5.2.3", | ||
"version": "5.2.4", | ||
"description": "Tiny (~5k), dependency-free Node.JS library to make your API more graceful", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
384
README.md
@@ -36,4 +36,4 @@ <h1 align="center"> | ||
- [NPM](#npm) | ||
- [Yarn](#yarn) | ||
- [PNPM](#pnpm) | ||
- [YARN](#yarn) | ||
- [Endpoint](#endpoint) | ||
@@ -43,16 +43,4 @@ - [/live](#live) | ||
- [Example](#example) | ||
- [ExpressJS](#expressjs) | ||
- [Fastify](#fastify) | ||
- [Koa](#koa) | ||
- [HTTP Server](#http-server) | ||
- [API](#api) | ||
- [GracefulServer](#gracefulserver) | ||
- [IGracefulServerOptions](#igracefulserveroptions) | ||
- [GracefulServer Instance](#gracefulserver-instance) | ||
- [API Doc](#api-doc) | ||
- [Integration with Docker](#integration-with-docker) | ||
- [HEALTH CHECK in Dockerfile](#health-check-in-dockerfile) | ||
- [Content of _healthcheck.js_](#content-of-healthcheckjs) | ||
- [Example of Dockerfile](#example-of-dockerfile) | ||
- [POC level](#poc-level) | ||
- [Company level](#company-level) | ||
- [Integration with Kubernetes](#integration-with-kubernetes) | ||
@@ -99,2 +87,8 @@ - [Thanks](#thanks) | ||
### YARN | ||
``` | ||
yarn add @gquittet/graceful-server | ||
``` | ||
## Endpoint | ||
@@ -134,368 +128,16 @@ | ||
### ExpressJS | ||
See: [EXAMPLE.md](./docs/EXAMPLE.md) | ||
The library works with the default HTTP NodeJS object. So, when you're using Express you can't pass | ||
directly the `app` object from Express. But, you can easily generate an HTTP NodeJS object from the `app` object. | ||
## API Doc | ||
Just follow the bottom example: | ||
See: [API.md](./docs/API.md) | ||
```javascript | ||
const express = require('express') | ||
const helmet = require('helmet') | ||
const http = require('http') | ||
const GracefulServer = require('@gquittet/graceful-server') | ||
const { connectToDb, closeDbConnection } = require('./db') | ||
const app = express() | ||
const server = http.createServer(app) | ||
const gracefulServer = GracefulServer(server, { closePromises: [closeDbConnection] }) | ||
app.use(helmet()) | ||
app.get('/test', (_, res) => { | ||
return res.send({ uptime: process.uptime() | 0 }) | ||
}) | ||
gracefulServer.on(GracefulServer.READY, () => { | ||
console.log('Server is ready') | ||
}) | ||
gracefulServer.on(GracefulServer.SHUTTING_DOWN, () => { | ||
console.log('Server is shutting down') | ||
}) | ||
gracefulServer.on(GracefulServer.SHUTDOWN, error => { | ||
console.log('Server is down because of', error.message) | ||
}) | ||
server.listen(8080, async () => { | ||
await connectToDb() | ||
gracefulServer.setReady() | ||
}) | ||
``` | ||
As you can see, we're using the `app` object from Express to set up the endpoints and middleware. | ||
But it can't listen (you can do it but `app` hasn't any liveness or readiness). The listening | ||
of HTTP calls need to be done by the default NodeJS HTTP object (aka **_server_**). | ||
### Fastify | ||
```javascript | ||
const fastify = require('fastify')({ logger: true }) | ||
const GracefulServer = require('@gquittet/graceful-server') | ||
const gracefulServer = GracefulServer(fastify.server) | ||
gracefulServer.on(GracefulServer.READY, () => { | ||
console.log('Server is ready') | ||
}) | ||
gracefulServer.on(GracefulServer.SHUTTING_DOWN, () => { | ||
console.log('Server is shutting down') | ||
}) | ||
gracefulServer.on(GracefulServer.SHUTDOWN, error => { | ||
console.log('Server is down because of', error.message) | ||
}) | ||
// Declare a route | ||
fastify.get('/', async (request, reply) => { | ||
return { hello: 'world' } | ||
}) | ||
// Run the server! | ||
const start = async () => { | ||
try { | ||
await fastify.listen({ port: 3000 }) | ||
fastify.log.info(`server listening on ${fastify.server.address().port}`) | ||
gracefulServer.setReady() | ||
} catch (err) { | ||
fastify.log.error(err) | ||
process.exit(1) | ||
} | ||
} | ||
start() | ||
``` | ||
**Be careful, if you are using Fastify v4.x.x with Node 16 and below**, you have to use | ||
```javascript | ||
await fastify.listen({ port: 3000, host: '0.0.0.0' }) | ||
``` | ||
because Node 16 and below does not support multiple addresses binding. | ||
See: https://github.com/fastify/fastify/issues/3536 | ||
### Koa | ||
```javascript | ||
const GracefulServer = require('@gquittet/graceful-server') | ||
const Koa = require('koa') | ||
const http = require('http') | ||
const Router = require('koa-router') | ||
const app = new Koa() | ||
const router = new Router() | ||
const server = http.createServer(app.callback()) | ||
gracefulServer = GracefulServer(server) | ||
router.get('/test') | ||
app.use(router.routes()) | ||
// response | ||
app.use(ctx => { | ||
ctx.body = 'Hello Koa' | ||
}) | ||
gracefulServer.on(GracefulServer.READY, () => { | ||
console.log('Server is ready') | ||
}) | ||
gracefulServer.on(GracefulServer.SHUTTING_DOWN, () => { | ||
console.log('Server is shutting down') | ||
}) | ||
gracefulServer.on(GracefulServer.SHUTDOWN, error => { | ||
console.log('Server is down because of', error.message) | ||
}) | ||
server.listen(8080, async () => { | ||
gracefulServer.setReady() | ||
}) | ||
``` | ||
As you can see, we're using the `app` object from Express to set up the endpoints and middleware. | ||
But it can't listen (you can do it but `app` hasn't any liveness or readiness). The listening | ||
of HTTP calls need to be done by the default NodeJS HTTP object (aka **_server_**). | ||
### HTTP Server | ||
```javascript | ||
import http from 'http' | ||
import url from 'url' | ||
import GracefulServer from '@gquittet/graceful-server' | ||
import { connectToDb, closeDbConnection } from './db' | ||
const server = http.createServer((req, res) => { | ||
if (req.url === '/test' && req.method === 'GET') { | ||
res.statusCode = 200 | ||
res.setHeader('Content-Type', 'application/json') | ||
return res.end(JSON.stringify({ uptime: process.uptime() | 0 })) | ||
} | ||
res.statusCode = 404 | ||
return res.end() | ||
}) | ||
const gracefulServer = GracefulServer(server, { closePromises: [closeDbConnection] }) | ||
gracefulServer.on(GracefulServer.READY, () => { | ||
console.log('Server is ready') | ||
}) | ||
gracefulServer.on(GracefulServer.SHUTTING_DOWN, () => { | ||
console.log('Server is shutting down') | ||
}) | ||
gracefulServer.on(GracefulServer.SHUTDOWN, error => { | ||
console.log('Server is down because of', error.message) | ||
}) | ||
server.listen(8080, async () => { | ||
await connectToDb() | ||
gracefulServer.setReady() | ||
}) | ||
``` | ||
## API | ||
### GracefulServer | ||
```typescript | ||
;((server: http.Server, options?: IGracefulServerOptions | undefined) => IGracefulServer) & typeof State | ||
``` | ||
where `State` is an enum that contains, `STARTING`, `READY`, `SHUTTING_DOWN` and `SHUTDOWN`. | ||
### IGracefulServerOptions | ||
All of the below options are optional. | ||
| Name | Type | Default | Description | | ||
|-------------------|:--------------------------:|:-------:|-----------------------------------------------------------------:| | ||
| syncClose | boolean | false | Run the closePromises in a series. | | ||
| closePromises | (() => Promise<unknown>)[] | [] | The functions to run when the API is stopping | | ||
| timeout | number | 1000 | The time in milliseconds to wait before shutting down the server | | ||
| healthCheck | boolean | true | Enable/Disable the default endpoints (liveness and readiness) | | ||
| kubernetes | boolean | false | Enable/Disable the kubernetes mode | | ||
| livenessEndpoint | string | /live | The liveness endpoint | | ||
| readinessEndpoint | string | /ready | The readiness endpoint | | ||
If you use Kubernetes, enable the kubernetes mode to let it handles the incoming traffic of your application. | ||
The Kubernetes mode will only work if you haven't disabled the health checks. | ||
### GracefulServer Instance | ||
```typescript | ||
export default interface IGracefulServer { | ||
isReady: () => boolean | ||
setReady: () => void | ||
on: (name: string, callback: (...args: any[]) => void) => EventEmitter | ||
} | ||
``` | ||
## Integration with Docker | ||
### HEALTH CHECK in Dockerfile | ||
See: [DOCKER.md](./docs/DOCKER.md) | ||
```Dockerfile | ||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s CMD ["node healthcheck.js"] | ||
``` | ||
### Content of _healthcheck.js_ | ||
```javascript | ||
const http = require('http') | ||
const options = { | ||
timeout: 2000, | ||
host: 'localhost', | ||
port: 8080, | ||
path: '/live' | ||
} | ||
const request = http.request(options, res => { | ||
console.info('STATUS:', res.statusCode) | ||
process.exitCode = res.statusCode === 200 ? 0 : 1 | ||
process.exit() | ||
}) | ||
request.on('error', err => { | ||
console.error('ERROR', err) | ||
process.exit(1) | ||
}) | ||
request.end() | ||
``` | ||
### Example of Dockerfile | ||
#### POC level | ||
```Dockerfile | ||
FROM node:12-slim | ||
WORKDIR /usr/src/app | ||
COPY package*.json ./ | ||
RUN npm ci --only=production | ||
COPY . . | ||
EXPOSE 8080 | ||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s CMD ["node healthcheck.js"] | ||
CMD [ "node", "server.js" ] | ||
``` | ||
#### Company level | ||
```Dockerfile | ||
FROM node:12-slim as base | ||
ENV NODE_ENV=production | ||
ENV TINI_VERSION=v0.18.0 | ||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini | ||
RUN chmod +x /tini && \ | ||
mkdir -p /node_app/app && \ | ||
chown -R node:node /node_app | ||
WORKDIR /node_app | ||
USER node | ||
COPY --chown=node:node package.json package-lock*.json ./ | ||
RUN npm ci && \ | ||
npm cache clean --force | ||
WORKDIR /node_app/app | ||
FROM base as source | ||
COPY --chown=node:node . . | ||
FROM source as dev | ||
ENV NODE_ENV=development | ||
ENV PATH=/node_app/node_modules/.bin:$PATH | ||
RUN npm install --only=development --prefix /node_app | ||
CMD ["nodemon", "--inspect=0.0.0.0:9229"] | ||
FROM source as test | ||
ENV NODE_ENV=development | ||
ENV PATH=/node_app/node_modules/.bin:$PATH | ||
COPY --from=dev /node_app/node_modules /node_app/node_modules | ||
RUN npm run lint | ||
ENV NODE_ENV=test | ||
RUN npm test | ||
CMD ["npm", "test"] | ||
FROM test as audit | ||
RUN npm audit --audit-level critical | ||
USER root | ||
ADD https://get.aquasec.com/microscanner / | ||
RUN chmod +x /microscanner && \ | ||
/microscanner your_token --continue-on-failure | ||
FROM source as buildProd | ||
ENV PATH=/node_app/node_modules/.bin:$PATH | ||
COPY --from=dev /node_app/node_modules /node_app/node_modules | ||
RUN npm run build | ||
FROM source as prod | ||
COPY --from=buildProd --chown=node:node /node_app/app/build ./build | ||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s CMD ["node healthcheck.js"] | ||
ENTRYPOINT ["/tini", "--"] | ||
CMD ["node", "./build/src/main.js"] | ||
``` | ||
## Integration with Kubernetes | ||
Don't forget to enable the kubernetes mode. [Check here](#igracefulserveroptions) (related to this [issue](https://github.com/gquittet/graceful-server/issues/5)) | ||
See: [KUBERNETES.md](./docs/KUBERNETES.md) | ||
```yml | ||
readinessProbe: | ||
httpGet: | ||
path: /ready | ||
port: 8080 | ||
failureThreshold: 1 | ||
initialDelaySeconds: 5 | ||
periodSeconds: 5 | ||
successThreshold: 1 | ||
timeoutSeconds: 5 | ||
livenessProbe: | ||
httpGet: | ||
path: /live | ||
port: 8080 | ||
failureThreshold: 3 | ||
initialDelaySeconds: 10 | ||
# Allow sufficient amount of time (90 seconds = periodSeconds * failureThreshold) | ||
# for the registered shutdown handlers to run to completion. | ||
periodSeconds: 30 | ||
successThreshold: 1 | ||
# Setting a very low timeout value (e.g. 1 second) can cause false-positive | ||
# checks and service interruption. | ||
timeoutSeconds: 5 | ||
# As per Kubernetes documentation (https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#when-should-you-use-a-startup-probe), | ||
# startup probe should point to the same endpoint as the liveness probe. | ||
# | ||
# Startup probe is only needed when container is taking longer to start than | ||
# `initialDelaySeconds + failureThreshold × periodSeconds` of the liveness probe. | ||
startupProbe: | ||
httpGet: | ||
path: /live | ||
port: 8080 | ||
failureThreshold: 3 | ||
initialDelaySeconds: 10 | ||
periodSeconds: 30 | ||
successThreshold: 1 | ||
timeoutSeconds: 5 | ||
``` | ||
## Thanks | ||
@@ -502,0 +144,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
25363
170