Saltar al contenido principal

Health Module

Overview

The health module exposes lightweight diagnostic endpoints used by:

  • Cloud Run liveness/readiness probesGET /health and GET /api/v1/pricing/health/live
  • Operations & on-callGET /health/database for detailed PostgreSQL diagnostics
  • Pricing subsystem probesGET /api/v1/pricing/health/*

All health endpoints are public (@IsPublic()) and require no authentication.


Architecture

The module follows the standard hexagonal (ports & adapters) layout:

health/
├── health.module.ts # Module definition
├── domain/
│ └── database-health.domain.ts # Port: IDatabaseHealthChecker + DatabaseHealthResult
├── application/
│ └── health.service.ts # Use case: orchestrates health responses
├── infrastructure/
│ ├── database-health.repository.ts # Adapter: Kysely-based PostgreSQL health checks
│ └── __tests__/
│ └── database-health.repository.spec.ts # Unit tests for the DB adapter
└── interfaces/
├── health.controller.ts # GET /health, GET /health/database
├── pricing-health.controller.ts # GET /api/v1/pricing/health/*
└── dtos/
└── health.dto.ts # Swagger response schemas

Layer responsibilities:

LayerResponsibility
domain/IDatabaseHealthChecker interface (port) and DatabaseHealthResult value object — no framework dependencies
application/HealthService orchestrates health use cases, depends only on the domain port
infrastructure/DatabaseHealthRepository implements IDatabaseHealthChecker using Kysely sql queries
interfaces/Thin controllers that map HTTP requests → application service → response DTOs

Dependency injection:

The DATABASE_HEALTH_CHECKER symbol token binds DatabaseHealthRepository to IDatabaseHealthChecker in the module providers. Controllers and the application service depend on the interface, never on the concrete implementation.


Endpoints

GET /health

Liveness probe for Cloud Run.

Always returns HTTP 200 and status: "ok" so the container is never restarted due to a database blip. Inspect database.isConnected for actual DB health.

{
"status": "ok",
"version": "0.0.145",
"firebaseProjectId": "flowpos-prod",
"database": {
"isConnected": true,
"responseTime": 12,
"userTableExists": true,
"databaseName": "flowpos_production",
"serverAddress": null,
"serverPort": null,
"currentUser": "flowpos_production",
"serverVersion": "PostgreSQL 17.0 ...",
"connectionMethod": "Unix Socket"
},
"timestamp": "2026-03-12T00:00:00.000Z"
}

GET /health/database

Detailed database diagnostics.

Returns status: "error" when the database is unreachable. Useful for debugging Cloud SQL VPC connector issues in staging/production.

{
"status": "ok",
"database": { ... },
"timestamp": "2026-03-12T00:00:00.000Z"
}

GET /api/v1/pricing/health

Basic liveness for the pricing subsystem. Returns status: "healthy" as long as the process is running.

GET /api/v1/pricing/health/detailed

Reports three component statuses:

ComponentHealthy when
databaseIDatabaseHealthChecker.testConnection() returns isConnected: true
cachePrice-cache hit rate ≥ 50 % after 100+ calls
featureFlagAlways healthy — reports ENABLE_PRICE_LISTS value

Overall status is the worst of all components (healthydegradedunhealthy).

GET /api/v1/pricing/health/ready

Readiness probe. Returns ready: true only when the database is reachable. Cloud Run will stop routing traffic if this returns ready: false.

GET /api/v1/pricing/health/live

Liveness probe. Always returns alive: true. If this stops responding, Cloud Run restarts the container.


Cloud Run probe configuration

Both controllers follow the Cloud Run probe contract:

  • Liveness (GET /health, GET /api/v1/pricing/health/live) — always 200; never kill the container due to DB downtime
  • Readiness (GET /api/v1/pricing/health/ready) — 200 only when DB is healthy

Recommended Cloud Run settings:

livenessProbe:
httpGet:
path: /health
initialDelaySeconds: 10
periodSeconds: 30

readinessProbe:
httpGet:
path: /api/v1/pricing/health/ready
initialDelaySeconds: 5
periodSeconds: 10

Design decisions

Why does GET /health always return 200? Cloud Run will restart the container if the liveness probe returns a non-2xx. A database blip should not cause a container restart — the app itself is healthy; only the DB connection is degraded. Operators should monitor database.isConnected via alerting rather than relying on container restarts.

Why follow hexagonal architecture for a simple health module? Consistency with all other backend modules. The domain port (IDatabaseHealthChecker) makes the health service testable without Kysely and allows swapping the DB check implementation without touching controllers or application logic.

Why does PricingHealthController live in HealthModule and not PricingModule? The readiness and liveness probes must always be available even if PricingModule fails to bootstrap. Hosting them in the always-loaded HealthModule guarantees the probes are reachable during partial startup failures.


Bruno API collection

All six endpoints are covered by Bruno requests at:

api-client/flowpos/collections/health/

Set BASE_URL to http://localhost:4000 in your Bruno environment. No auth headers are required.