EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
db.js
Go to the documentation of this file.
1/**
2 * @file PostgreSQL Database Connection Pool
3 * @module services/db
4 * @description
5 * Centralized PostgreSQL connection pool using node-postgres (pg). Provides database
6 * connectivity for all backend services with automatic connection management, pooling,
7 * and error handling.
8 *
9 * **Configuration Priority:**
10 * 1. Root `.env` file - Shared configuration for DevOps scripts
11 * 2. Backend `.env` file - Overrides for backend-specific settings
12 *
13 * **Connection Modes:**
14 * - **Managed Database** (DigitalOcean, Heroku): Uses `DATABASE_URL` connection string
15 * - **Self-Hosted**: Uses individual environment variables (DB_HOST, DB_PORT, etc.)
16 *
17 * **SSL Handling:**
18 * - Automatically enables SSL for DATABASE_URL or if DB_SSL=true
19 * - Uses `rejectUnauthorized: false` for managed databases with self-signed certs
20 * - Strips `sslmode=require` from URL to prevent pg conflicts
21 *
22 * **Environment Variables:**
23 * - `DATABASE_URL` - PostgreSQL connection string (overrides individual vars)
24 * - `DB_HOST` - Database host (default: DigitalOcean managed DB)
25 * - `DB_PORT` - Database port (default: 25060)
26 * - `DB_USER` - Database username (default: doadmin)
27 * - `DB_PASSWORD` - Database password (strips quotes)
28 * - `DB_NAME` - Database name (default: defaultdb)
29 * - `DB_SSL` - Enable SSL (default: true)
30 *
31 * **Pool Configuration:**
32 * - Uses pg.Pool defaults (10 max connections, 30s idle timeout)
33 * - Automatic connection reuse and recycling
34 * - Event handlers for connection lifecycle
35 *
36 * **Error Handling:**
37 * - Logs unexpected errors on idle clients
38 * - Exits process on connection pool errors (requires restart)
39 * - Prevents silent connection failures
40 * @exports {Pool} PostgreSQL connection pool instance
41 * @example
42 * // Query with parameterized values (SQL injection safe)
43 * const pool = require('./services/db');
44 * const result = await pool.query(
45 * 'SELECT * FROM customers WHERE tenant_id = $1',
46 * [tenantId]
47 * );
48 * console.log(result.rows);
49 * @example
50 * // Transaction with client checkout
51 * const pool = require('./services/db');
52 * const client = await pool.connect();
53 * try {
54 * await client.query('BEGIN');
55 * await client.query('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [100, 1]);
56 * await client.query('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [100, 2]);
57 * await client.query('COMMIT');
58 * } catch (e) {
59 * await client.query('ROLLBACK');
60 * throw e;
61 * } finally {
62 * client.release();
63 * }
64 * @example
65 * // Bulk insert with transaction
66 * const pool = require('./services/db');
67 * const client = await pool.connect();
68 * try {
69 * await client.query('BEGIN');
70 * for (const row of data) {
71 * await client.query('INSERT INTO metrics (...) VALUES (...)', row);
72 * }
73 * await client.query('COMMIT');
74 * } finally {
75 * client.release();
76 * }
77 * @exports pool - PostgreSQL connection pool instance
78 * @see {@link https://node-postgres.com/} node-postgres documentation
79 * @see {@link https://www.postgresql.org/docs/} PostgreSQL documentation
80 * @see {@link module:middleware/tenant} for tenant-scoped queries
81 * @requires pg - PostgreSQL client library
82 * @requires dotenv - Environment variable loader
83 */
84
85const { Pool } = require('pg');
86const path = require('path');
87
88// Load env in this order:
89// 1) Project root .env (used by devops scripts and shared config)
90// 2) backend/.env overrides root values for backend-specific settings
91require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });
92require('dotenv').config({ path: path.resolve(__dirname, '../.env'), override: true });
93
94// Support both individual vars and DATABASE_URL for managed DB
95const dbConfig = process.env.DATABASE_URL
96 ? (() => {
97 // Strip sslmode=require from URL to ensure pg respects explicit ssl options
98 const cleanedUrl = process.env.DATABASE_URL.replace(/([?&])sslmode=require(&|$)/, (m, p1, p2) => (p2 === '&' ? p1 : ''));
99 return {
100 connectionString: cleanedUrl,
101 ssl: process.env.DB_SSL === 'true' || process.env.DATABASE_URL.includes('sslmode=require')
102 ? { rejectUnauthorized: false }
103 : false,
104 };
105 })()
106 : {
107 host: process.env.DB_HOST || 'rmm-psa-db-do-user-28531160-0.i.db.ondigitalocean.com',
108 port: parseInt(process.env.DB_PORT || '25060', 10),
109 user: process.env.DB_USER || 'doadmin',
110 password: (process.env.DB_PASSWORD || 'AVNS_J8RJAmsEwsHFG52_-F2').replace(/^"|"$/g, ''),
111 database: process.env.DB_NAME || 'defaultdb',
112 ssl: (process.env.DB_SSL === 'false' ? false : true)
113 ? { rejectUnauthorized: false }
114 : false,
115 };
116
117const pool = new Pool(dbConfig);
118
119// Test connection and log status
120let logged = false;
121pool.on('connect', () => {
122 if (!logged) {
123 console.log('[DB] Connected to PostgreSQL');
124 logged = true;
125 }
126});
127
128pool.on('error', (err) => {
129 console.error('[DB] Unexpected error on idle client', err);
130 process.exit(-1);
131});
132
133module.exports = pool;