🚀 Graceful Server 🐢
Tiny (~5k), dependency-free Node.JS library to make your API more graceful
Features
✔ It's listening system events to gracefully close your API on interruption.
✔ It facilitates the disconnect of data sources on shutdown.
✔ It facilitates the use of liveness and readiness.
✔ It manages the connections of your API.
✔ It avoid boilerplate codes.
Requirements
✔ NodeJS >= 8
Installation
NPM
npm install --save @gquittet/graceful-server
Yarn
yarn add @gquittet/graceful-server
Endpoint
Below you can find the default endpoint but you can setup them. To do that, check out the Options part.
/live
The endpoint responds:
200
status code with the uptime of the server in second.
{ "uptime": 42 }
Used to configure liveness probe.
/ready
The endpoint responds:
200
status code if the server is ready.
{ "status": "ready" }
503
status code with an empty response if the server is not ready (started, shutting down, etc).
Example
ExpressJS
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()
app.disable('x-powered-by')
app.get('/live', () => {})
app.get('/ready', () => {})
app.use(helmet())
app.get('/test', (_, res) => {
return res.send({ uptime: process.uptime() | 0 })
})
const server = http.createServer(app)
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()
})
HTTP Server
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
((server: http.Server, options?: IGracefulServerOptions | undefined) => IGracefulServer) & typeof State
where State
is an enum that contains READY
, SHUTTING_DOWN
and SHUTDOWN
.
IGracefulServerOptions
All of the below options are optional.
Name | Type | Default | Description |
---|
closePromises | (() => Promise)[] | [] | The functions to run when the API is stopping |
timeout | number | 1000 | The time in milliseconds to wait before shutting down the server |
livenessEndpoint | string | /live | The liveness endpoint |
readinessEndpoint | string | /ready | The readiness endpoint |
GracefulServer Instance
export default interface IGracefulServer {
isReady: () => Boolean
setReady: () => void
on: (name: string, callback: (...args: any[]) => void) => EventEmitter,
}
Integration with Docker
HEALTH CHECK in Dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s CMD ["node healthcheck.js"]
Content of healthcheck.js
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
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
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
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 5
startupProbe:
httpGet:
path: /live
port: 8080
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 5
Thanks
★ Terminus
★ Lightship
★ Stoppable
★ http-terminator
★ Bret Fisher for its great articles and videos
★ IBM documentation
★ Node HTTP documentation
★ Cloud Health
★ Cloud Health Connect
Donate
If you like my job, don't hesite to contribute to this project! ❤️