From acf9f571238e69c2a97a2cf8260203bdda3668d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0mol=C3=ADk?= Date: Sun, 5 May 2024 13:35:49 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20express=20middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +++ README.md | 3 ++- demo/express-mw.ts | 31 ++++++++++++++++++++++++++++ demo/express.ts | 2 +- src/express.test.ts | 26 +++++++++++++++++++++++ src/express.ts | 50 +++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 3 +++ 7 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 demo/express-mw.ts create mode 100644 src/express.test.ts create mode 100644 src/express.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fe9981e..79586e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +### Added +- Express.js middleware + ## [2.0.0-rc.2] 2024-05-04 ### Added diff --git a/README.md b/README.md index 92289c9..2049f87 100644 --- a/README.md +++ b/README.md @@ -45,4 +45,5 @@ curl http://localhost:46461/healthz --verbose | jq ### Guides -- [How to use with ExpressJS](./demo/express.ts) \ No newline at end of file +- [How to use as ExpressJS middleware](./demo/express-mw.ts) +- [How to use Core with ExpressJS](./demo/express.ts) \ No newline at end of file diff --git a/demo/express-mw.ts b/demo/express-mw.ts new file mode 100644 index 0000000..bf4d7f4 --- /dev/null +++ b/demo/express-mw.ts @@ -0,0 +1,31 @@ +// To run demo: npx ts-node demo/express-mw.ts + +import express from 'express' +import * as healthz from '../dist/index' + +const app = express() + +app.use((req, res, next) => { + healthz.express({ + // Define checks + checks: [ + { + id: 'PostgreSQL', + required: true, + fn: async () => new Promise((resolve) => setTimeout(resolve, 1000)), + }, + { + id: 'Redis', + fn: async () => new Promise((resolve) => setTimeout(resolve, 10)), + }, + ], + // Make timeout configurable from the outside + timeout: parseInt(String(req.query.timeout)) || undefined, + // Modify the result if needed + transformResult: (x) => (console.log(x), x), + })(req, res, next) +}) + +const running = app.listen(process.env.PORT ?? 0) +const port = (running.address() as any).port +console.log(`Express app running on http://localhost:${port}/healthz`) diff --git a/demo/express.ts b/demo/express.ts index 0ff600f..dd60785 100644 --- a/demo/express.ts +++ b/demo/express.ts @@ -36,4 +36,4 @@ app.use('/healthz', async (req, res, next) => { const running = app.listen(process.env.PORT ?? 0) const port = (running.address() as any).port -console.log(`Express app running on http://localhost:${port}`) +console.log(`Express app running on http://localhost:${port}/healthz`) diff --git a/src/express.test.ts b/src/express.test.ts new file mode 100644 index 0000000..9710209 --- /dev/null +++ b/src/express.test.ts @@ -0,0 +1,26 @@ +import test, { describe } from 'node:test' +import express from 'express' +import { middleware } from './express' +import { equal } from 'node:assert' +import { Result } from './healthz' +import { AddressInfo } from 'node:net' + +describe('Express integration', () => { + test('Middleware', async () => { + const app = express() + app.use(middleware({ + checks: [ + { + id: 'a', + fn: async () => 1, + } + ] + })) + const started = app.listen(0) + const response = await fetch(`http://localhost:${(started.address() as AddressInfo).port}/healthz`) + equal(response.status, 200) + started.close() + const body: Result = await response.json() + equal(body.status, 'OK') + }) +}) \ No newline at end of file diff --git a/src/express.ts b/src/express.ts new file mode 100644 index 0000000..b0713d8 --- /dev/null +++ b/src/express.ts @@ -0,0 +1,50 @@ +import { status } from '.' +import { check, Option as HOption, Result } from './healthz' +import { htmlResult, jsonResult } from './http' + +export interface Option extends HOption { + transformResult?: (result: Result) => Result +} + +type Request = { + header: (arg: string) => string | undefined + query: Record + path: string +} + +type Response = { + status: (status: number) => Response + type: (t: string) => any + end: (t: string) => any + json: (t: any) => any +} + +export function middleware( + option?: Option, +) { + const transformResult: Option['transformResult'] = + option?.transformResult ?? ((x) => x) + return async function healthzmw(req: Req, res: Res, next: any) { + try { + if (req.path !== '/healthz') { + return next() + } + const health = transformResult( + await check({ + checks: option?.checks, + timeout: option?.timeout, + }), + ) + + res.status(status(health)) + if (req.header('accept')?.includes('html')) { + res.type('html') + res.end(htmlResult(health)) + } else { + res.json(jsonResult(health)) + } + } catch (error) { + next(error) + } + } +} diff --git a/src/index.ts b/src/index.ts index f2be426..52e9f14 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,3 +13,6 @@ export { jsonResult as json, resultStatusCode as status, } from './http' +export { + middleware as express +} from './express'