Skip to content

Commit

Permalink
Merge pull request docmost#64 from docmost/feat/health
Browse files Browse the repository at this point in the history
Add health check
  • Loading branch information
Philipinho authored Jul 6, 2024
2 parents 7383673 + a0536d8 commit c4c169b
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 5 deletions.
1 change: 1 addition & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-fastify": "^10.3.9",
"@nestjs/platform-socket.io": "^10.3.9",
"@nestjs/terminus": "^10.2.3",
"@nestjs/websockets": "^10.3.9",
"@react-email/components": "0.0.19",
"@react-email/render": "^0.0.15",
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { MailModule } from './integrations/mail/mail.module';
import { QueueModule } from './integrations/queue/queue.module';
import { StaticModule } from './integrations/static/static.module';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { HealthModule } from './integrations/health/health.module';

@Module({
imports: [
Expand All @@ -21,6 +22,7 @@ import { EventEmitterModule } from '@nestjs/event-emitter';
WsModule,
QueueModule,
StaticModule,
HealthModule,
StorageModule.forRootAsync({
imports: [EnvironmentModule],
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ export class TransformHttpResponseInterceptor<T>
intercept(
context: ExecutionContext,
next: CallHandler<T>,
): Observable<Response<T>> {
): Observable<Response<T> | any> {
const request = context.switchToHttp().getRequest();
const path = request.url;

// Skip interceptor for the /api/health path
if (path === '/api/health') {
return next.handle();
}

return next.handle().pipe(
map((data) => {
const status = context.switchToHttp().getResponse().statusCode;
Expand Down
5 changes: 4 additions & 1 deletion apps/server/src/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export class CoreModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(DomainMiddleware)
.exclude({ path: 'auth/setup', method: RequestMethod.POST })
.exclude(
{ path: 'auth/setup', method: RequestMethod.POST },
{ path: 'health', method: RequestMethod.GET },
)
.forRoutes('*');
}
}
4 changes: 3 additions & 1 deletion apps/server/src/database/database.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ types.setTypeParser(types.builtins.INT8, (val) => Number(val));
dialect: new PostgresDialect({
pool: new Pool({
connectionString: environmentService.getDatabaseURL(),
}).on('error', (err) => {
console.error('Database error:', err.message);
}),
}),
plugins: [new CamelCasePlugin()],
Expand Down Expand Up @@ -102,7 +104,7 @@ export class DatabaseModule implements OnModuleDestroy, OnApplicationBootstrap {
}

async establishConnection() {
const retryAttempts = 10;
const retryAttempts = 15;
const retryDelay = 3000;

this.logger.log('Establishing database connection');
Expand Down
22 changes: 22 additions & 0 deletions apps/server/src/integrations/health/health.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
import { PostgresHealthIndicator } from './postgres.health';
import { RedisHealthIndicator } from './redis.health';

@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private postgres: PostgresHealthIndicator,
private redis: RedisHealthIndicator,
) {}

@Get()
@HealthCheck()
async check() {
return this.health.check([
() => this.postgres.pingCheck('database'),
() => this.redis.pingCheck('redis'),
]);
}
}
13 changes: 13 additions & 0 deletions apps/server/src/integrations/health/health.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Global, Module } from '@nestjs/common';
import { HealthController } from './health.controller';
import { TerminusModule } from '@nestjs/terminus';
import { PostgresHealthIndicator } from './postgres.health';
import { RedisHealthIndicator } from './redis.health';

@Global()
@Module({
controllers: [HealthController],
providers: [PostgresHealthIndicator, RedisHealthIndicator],
imports: [TerminusModule],
})
export class HealthModule {}
31 changes: 31 additions & 0 deletions apps/server/src/integrations/health/postgres.health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { InjectKysely } from 'nestjs-kysely';
import {
HealthCheckError,
HealthIndicator,
HealthIndicatorResult,
} from '@nestjs/terminus';
import { Injectable, Logger } from '@nestjs/common';
import { sql } from 'kysely';
import { KyselyDB } from '@docmost/db/types/kysely.types';

@Injectable()
export class PostgresHealthIndicator extends HealthIndicator {
private readonly logger = new Logger(PostgresHealthIndicator.name);

constructor(@InjectKysely() private readonly db: KyselyDB) {
super();
}

async pingCheck(key: string): Promise<HealthIndicatorResult> {
try {
await sql`SELECT 1=1`.execute(this.db);
return this.getStatus(key, true);
} catch (e) {
this.logger.error(JSON.stringify(e));
throw new HealthCheckError(
`${key} is not available`,
this.getStatus(key, false),
);
}
}
}
34 changes: 34 additions & 0 deletions apps/server/src/integrations/health/redis.health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
HealthCheckError,
HealthIndicator,
HealthIndicatorResult,
} from '@nestjs/terminus';
import { Injectable, Logger } from '@nestjs/common';
import { EnvironmentService } from '../environment/environment.service';
import { Redis } from 'ioredis';

@Injectable()
export class RedisHealthIndicator extends HealthIndicator {
private readonly logger = new Logger(RedisHealthIndicator.name);

constructor(private environmentService: EnvironmentService) {
super();
}

async pingCheck(key: string): Promise<HealthIndicatorResult> {
try {
const redis = new Redis(this.environmentService.getRedisUrl(), {
maxRetriesPerRequest: 15,
});

await redis.ping();
return this.getStatus(key, true);
} catch (e) {
this.logger.error(e);
throw new HealthCheckError(
`${key} is not available`,
this.getStatus(key, false),
);
}
}
}
3 changes: 2 additions & 1 deletion apps/server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ async function bootstrap() {
.addHook('preHandler', function (req, reply, done) {
if (
req.originalUrl.startsWith('/api') &&
!req.originalUrl.startsWith('/api/auth/setup')
!req.originalUrl.startsWith('/api/auth/setup') &&
!req.originalUrl.startsWith('/api/health')
) {
if (!req.raw?.['workspaceId']) {
throw new NotFoundException('Workspace not found');
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"client:dev": "nx run client:dev",
"server:dev": "nx run server:start:dev",
"server:start": "nx run server:start:prod",
"email:dev": "nx run @docmost/transactional:dev"
"email:dev": "nx run @docmost/transactional:dev",
"dev": "pnpm concurrently -n \"frontend,backend\" -c \"cyan,green\" \"pnpm run client:dev\" \"pnpm run server:dev\""
},
"dependencies": {
"@docmost/editor-ext": "workspace:*",
Expand Down Expand Up @@ -66,6 +67,7 @@
"@nx/js": "19.3.2",
"@types/uuid": "^10.0.0",
"nx": "19.3.2",
"concurrently": "^8.2.2",
"tsx": "^4.15.7"
},
"workspaces": {
Expand Down
Loading

0 comments on commit c4c169b

Please sign in to comment.