Building High-Throughput REST APIs with Node.js, Express & PostgreSQL
What You Will Master in This Tutorial
- Architect clean modular Express applications with controllers, services, and repository layers
- Configure enterprise PostgreSQL connection pooling to handle thousands of concurrent queries
- Implement parameterized SQL queries to completely neutralize SQL injection vulnerabilities
- Deploy on headless cloud servers with PM2 process management and reverse proxying
import pg from 'pg';
const { Pool } = pg;
export const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Max concurrent connections in pool
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
export async function query(text, params) {
const start = Date.now();
const res = await pool.query(text, params);
const duration = Date.now() - start;
if (duration > 150) {
console.warn(`[SLOW QUERY] ${duration}ms: ${text}`);
}
return res;
}
Frequently Asked Questions
Going deeper: production REST APIs in practice
The patterns in this tutorial β modular controllers, services, and repositories on top of a pooled PostgreSQL connection β are what separate a demo API from one that survives production traffic. In practice, the pool configuration does the heavy lifting: capping concurrent connections, timing out idle clients, and logging slow queries turns the database from a bottleneck into a measured resource. Combined with parameterized queries everywhere, the same codebase that handles a tutorial workload scales to thousands of concurrent requests without an architectural rewrite, and deploys cleanly behind a process manager and reverse proxy on a headless server. None of this requires exotic tooling β it requires discipline about the boundaries between layers and respect for the database as a shared, finite resource.
Common mistakes
- Opening a new connection per request. Skipping the pool means every query pays TCP and authentication overhead and Postgres hits its connection limit under modest load; share one configured pool across the app.
- Building SQL with string concatenation. Interpolating user input into query strings is the classic SQL injection hole; parameterized queries with placeholders neutralize it completely and keep query plans cacheable.
- Business logic inside route handlers. Fat controllers that mix HTTP parsing, validation, business rules, and SQL become untestable; keep handlers thin and push logic into services and repositories where it can be unit-tested without HTTP.
- Ignoring slow-query logs. The pool wrapper in this tutorial logs anything over 150ms for a reason: unlogged slow queries are how a missing index quietly becomes a production outage.
Frequently asked questions
Should I use an ORM or raw SQL for a serious API?
Raw SQL with parameterized queries gives maximum performance and transparent query plans, while lightweight query builders add type safety without engine overhead β choose per team experience, but keep queries parameterized either way.
How large should my connection pool be?
Start from your database's capacity, not your hopes: the tutorial's pool caps at 20 concurrent connections, and the right number is the smallest one that keeps response times flat under your real load test, raised only when the database β not the app tier β proves it has headroom.
How do I run this API in production?
Deploy on a headless cloud server under PM2 for process management and restart resilience, behind a reverse proxy that terminates TLS and forwards traffic to Express, with environment variables β never committed code β carrying the database connection string.