Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/config/index.ts
Original file line number Diff line number Diff line change
@@ -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),
},
};
46 changes: 42 additions & 4 deletions src/db.ts
Original file line number Diff line number Diff line change
@@ -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',
});
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<import('pg').QueryResult> => 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',
};
}
}
15 changes: 13 additions & 2 deletions src/routes/health.ts
Original file line number Diff line number Diff line change
@@ -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);
});

Expand Down
8 changes: 7 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
Loading