-
-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(database): Add database health indicator
- Loading branch information
1 parent
fceb4ba
commit bdd4652
Showing
24 changed files
with
940 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
FROM node:latest | ||
|
||
WORKDIR /usr/src/app | ||
|
||
COPY package*.json ./ | ||
|
||
RUN npm install | ||
|
||
COPY . . | ||
|
||
CMD ["npm", "test"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
version: "3" | ||
|
||
services: | ||
|
||
lib: | ||
build: | ||
context: . | ||
networks: | ||
- overlay | ||
|
||
networks: | ||
overlay: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { INestApplication, DynamicModule } from '@nestjs/common'; | ||
import { Test } from '@nestjs/testing'; | ||
import { TerminusOptions } from '@godaddy/terminus'; | ||
import { | ||
DatabaseHealthIndicator, | ||
TerminusModuleOptions, | ||
TerminusModule, | ||
} from '../../lib'; | ||
import { HTTP_SERVER_REF, NestFactory } from '@nestjs/core'; | ||
import * as http from 'http'; | ||
|
||
import { TypeOrmModule } from '@nestjs/typeorm'; | ||
import Axios from 'axios'; | ||
|
||
describe('Database Health', () => { | ||
let app: INestApplication; | ||
const PORT = process.env.PORT || 3001; | ||
|
||
const getTerminusOptions = ( | ||
db: DatabaseHealthIndicator, | ||
): TerminusModuleOptions => ({ | ||
endpoints: [ | ||
{ | ||
url: '/health', | ||
healthIndicators: [async () => db.pingCheck('database')], | ||
}, | ||
], | ||
}); | ||
|
||
class ApplicationModule { | ||
static forRoot(options): DynamicModule { | ||
return { | ||
module: ApplicationModule, | ||
imports: [ | ||
TypeOrmModule.forRoot({ | ||
type: 'sqlite', | ||
database: 'test', | ||
keepConnectionAlive: true, | ||
}), | ||
TerminusModule.forRootAsync(options), | ||
], | ||
}; | ||
} | ||
} | ||
|
||
async function bootstrapModule(options) { | ||
app = await NestFactory.create(ApplicationModule.forRoot(options)); | ||
await app.listen(PORT); | ||
} | ||
|
||
it('should check if the database is available', async () => { | ||
await bootstrapModule({ | ||
inject: [DatabaseHealthIndicator], | ||
useFactory: getTerminusOptions, | ||
}); | ||
|
||
const response = await Axios.get(`http://0.0.0.0:${PORT}/health`); | ||
expect(response.status).toBe(200); | ||
expect(response.data).toEqual({ | ||
status: 'ok', | ||
info: { database: { status: 'up' } }, | ||
}); | ||
}); | ||
|
||
it('should throw an error if runs into timeout error', async () => { | ||
await bootstrapModule({ | ||
inject: [DatabaseHealthIndicator], | ||
useFactory: (db: DatabaseHealthIndicator): TerminusModuleOptions => ({ | ||
endpoints: [ | ||
{ | ||
url: '/health', | ||
healthIndicators: [ | ||
async () => db.pingCheck('database', { timeout: 1 }), | ||
], | ||
}, | ||
], | ||
}), | ||
}); | ||
|
||
try { | ||
await Axios.get(`http://0.0.0.0:${PORT}/health`, {}); | ||
} catch (error) { | ||
expect(error.response.status).toBe(503); | ||
expect(error.response.data).toEqual({ | ||
status: 'error', | ||
error: { | ||
database: { | ||
status: 'down', | ||
message: 'Database did not respond after 1ms', | ||
}, | ||
}, | ||
}); | ||
} | ||
}); | ||
|
||
afterEach(async () => { | ||
app.close(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,5 +17,6 @@ | |
"coverageReporters": [ | ||
"json", | ||
"lcov" | ||
] | ||
], | ||
"testEnvironment": "node" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,22 @@ | ||
{ | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"tsx", | ||
"js", | ||
"json" | ||
], | ||
"transform": { | ||
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js" | ||
}, | ||
"testRegex": "/src/.*\\.(test|spec).(ts|tsx|js)$", | ||
"collectCoverageFrom": [ | ||
"src/**/*.{js,jsx,tsx,ts}", | ||
"!**/node_modules/**", | ||
"!**/vendor/**" | ||
], | ||
"coverageReporters": [ | ||
"json", | ||
"lcov" | ||
] | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"tsx", | ||
"js", | ||
"json" | ||
], | ||
"transform": { | ||
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js" | ||
}, | ||
"testRegex": "/src/.*\\.(test|spec).(ts|tsx|js)$", | ||
"collectCoverageFrom": [ | ||
"src/**/*.{js,jsx,tsx,ts}", | ||
"!**/node_modules/**", | ||
"!**/vendor/**" | ||
], | ||
"coverageReporters": [ | ||
"json", | ||
"lcov" | ||
], | ||
"testEnvironment": "node" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { HealthCheckError } from '@godaddy/terminus'; | ||
|
||
export class ConnectionNotFoundError extends HealthCheckError { | ||
constructor(cause) { | ||
super('Connection provider not found in application context', cause); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { Injectable, Optional } from '@nestjs/common'; | ||
import { HealthIndicatorResult } from '../..'; | ||
import { Connection } from 'typeorm'; | ||
import { HealthCheckError } from '@godaddy/terminus'; | ||
import { ConnectionNotFoundError } from './connection-not-found.error'; | ||
import { | ||
promiseTimeout, | ||
TimeoutError as PromiseTimeoutError, | ||
} from '../../utils'; | ||
import { TimeoutError } from './timeout-error'; | ||
|
||
export interface DatabasePingCheckSettings { | ||
connection?: Connection; | ||
timeout?: number; | ||
} | ||
|
||
@Injectable() | ||
export class DatabaseHealthIndicator { | ||
constructor(@Optional() private readonly connection: Connection) {} | ||
|
||
private getStatus(key: string, isHealthy: boolean, options?: any) { | ||
return { | ||
[key]: { | ||
status: isHealthy ? 'up' : 'down', | ||
...options, | ||
}, | ||
}; | ||
} | ||
|
||
private async pingDb(connection, timeout) { | ||
return await promiseTimeout(timeout, connection.query('SELECT 1')); | ||
} | ||
|
||
async pingCheck( | ||
key: string, | ||
options: DatabasePingCheckSettings = {}, | ||
): Promise<HealthIndicatorResult> { | ||
let isHealthy = false; | ||
const connection = options.connection || this.connection; | ||
const timeout = options.timeout || 1000; | ||
|
||
if (!connection) { | ||
throw new ConnectionNotFoundError( | ||
this.getStatus(key, isHealthy, { | ||
message: 'Connection provider not found in application context', | ||
}), | ||
); | ||
} | ||
|
||
try { | ||
await this.pingDb(connection, timeout); | ||
isHealthy = true; | ||
} catch (err) { | ||
if (err instanceof PromiseTimeoutError) { | ||
throw new TimeoutError( | ||
timeout, | ||
this.getStatus(key, isHealthy, { | ||
message: `Database did not respond after ${timeout}ms`, | ||
}), | ||
); | ||
} | ||
} | ||
|
||
if (isHealthy) { | ||
return this.getStatus(key, isHealthy); | ||
} else { | ||
throw new HealthCheckError( | ||
'Database is not available', | ||
this.getStatus(key, isHealthy), | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { HealthCheckError } from '@godaddy/terminus'; | ||
|
||
export class TimeoutError extends HealthCheckError { | ||
constructor(timeout, cause) { | ||
super(`Database did not respond after ${timeout}ms`, cause); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './database/database.health'; | ||
export * from './database/connection-not-found.error'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './interfaces'; | ||
export * from './terminus.module'; | ||
export * from './health-indicators'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,8 @@ | ||
export type HealthIndicatorResult = { | ||
[key: string]: any; | ||
[key: string]: { | ||
status: string; | ||
[optionalKeys: string]: any; | ||
}; | ||
}; | ||
|
||
export type HealthIndicatorFunction = () => Promise<HealthIndicatorResult>; | ||
|
||
/** | ||
* Represents a health indicator of a health check | ||
*/ | ||
export interface HealthIndicator { | ||
/** | ||
* If the health indicator is healthy | ||
* | ||
* @param {string} key The key of the health check which will be used in the result object | ||
* @param {any} [options] The options to configure the health indicator | ||
*/ | ||
isHealthy(key: string, options?: any): Promise<HealthIndicatorResult>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.