diff --git a/src/config/index.ts b/src/config/index.ts index f7487a4..1fc6a81 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,4 +1,20 @@ export const config = { - port: process.env.PORT ?? 3000, + port: Number(process.env.PORT ?? 3000), nodeEnv: process.env.NODE_ENV ?? 'development', + /** + * Primary PostgreSQL connection string used by the shared pg.Pool. + * Example (matches docker-compose): postgresql://postgres:postgres@postgres:5432/callora?schema=public + */ + databaseUrl: + process.env.DATABASE_URL ?? + 'postgresql://postgres:postgres@localhost:5432/callora?schema=public', + /** + * Connection pool tuning. These can be overridden via environment variables + * but have sensible defaults for local development. + */ + dbPool: { + max: Number(process.env.DB_POOL_MAX ?? 10), + idleTimeoutMillis: Number(process.env.DB_IDLE_TIMEOUT_MS ?? 30_000), + connectionTimeoutMillis: Number(process.env.DB_CONN_TIMEOUT_MS ?? 2_000), + }, }; diff --git a/src/db.ts b/src/db.ts index 9f9f875..5347dad 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,7 +1,45 @@ import { Pool } from 'pg'; +import { config } from './config/index.js'; +import { logger } from './logger.js'; -// Initialize the Postgres connection pool +/** + * Shared PostgreSQL connection pool for the application. + * + * Pool configuration: + * - connectionString: taken from config.databaseUrl (DATABASE_URL env var) + * - max: maximum number of concurrent clients in the pool (DB_POOL_MAX, default 10) + * - idleTimeoutMillis: how long idle clients stay open before being closed (DB_IDLE_TIMEOUT_MS, default 30s) + * - connectionTimeoutMillis: how long to wait when acquiring a client from the pool (DB_CONN_TIMEOUT_MS, default 2s) + */ export const pool = new Pool({ -// Use the env var, or fallback to a default local test database URL -connectionString: process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/callora_test', -}); \ No newline at end of file + connectionString: config.databaseUrl, + max: config.dbPool.max, + idleTimeoutMillis: config.dbPool.idleTimeoutMillis, + connectionTimeoutMillis: config.dbPool.connectionTimeoutMillis, +}); + +/** + * Convenience helper that proxies to pool.query for simple one-off queries. + */ +export const query = ( + text: string, + params?: unknown[], +): Promise => pool.query(text, params); + +/** + * Lightweight database health check used by the /api/health endpoint. + * Returns { ok: true } when a simple `SELECT 1` succeeds, or { ok: false, error } + * when the database is unreachable or misconfigured. + */ +export async function checkDbHealth(): Promise<{ ok: boolean; error?: string }> { + try { + await pool.query('SELECT 1'); + return { ok: true }; + } catch (error) { + logger.error('[db] health check failed', error); + return { + ok: false, + error: error instanceof Error ? error.message : 'Unknown database error', + }; + } +} \ No newline at end of file diff --git a/src/routes/health.ts b/src/routes/health.ts index 697eebb..a6efd89 100644 --- a/src/routes/health.ts +++ b/src/routes/health.ts @@ -1,10 +1,21 @@ import { Router } from 'express'; import type { HealthResponse } from '../types/index.js'; +import { checkDbHealth } from '../db.js'; const router = Router(); -router.get('/', (_req, res) => { - const response: HealthResponse = { status: 'ok', service: 'callora-backend' }; +router.get('/', async (_req, res) => { + const db = await checkDbHealth(); + + const response: HealthResponse = { + status: db.ok ? 'ok' : 'degraded', + service: 'callora-backend', + db: { + status: db.ok ? 'ok' : 'error', + ...(db.ok ? {} : { error: db.error }), + }, + }; + res.json(response); }); diff --git a/src/types/index.ts b/src/types/index.ts index be93743..bc700fa 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,6 +1,12 @@ +export interface DbHealthStatus { + status: 'ok' | 'error'; + error?: string; +} + export interface HealthResponse { - status: string; + status: 'ok' | 'degraded'; service: string; + db?: DbHealthStatus; } export interface ApisResponse {