Il tuo backend Node.js funziona, ma ogni query al database sembra una trattativa con un fornitore che non risponde mai. Tempi di risposta che salgono, connessioni che si accumulano, e il client che ti chiede perché "il sito è lento". Il problema quasi mai è Node o PostgreSQL presi singolarmente. È il modo in cui li colleghi.
Noi, di Meteora Web, ci lavoriamo ogni giorno. Abbiamo visto progetti rallentare non per colpa del database, ma per driver configurati male o query builder usati come stampelle. In questa guida ti mostriamo la differenza sostanziale tra usare il driver pg direttamente e affidarsi a Knex, quando conviene l'uno e quando l'altro, e come evitare gli errori che costano cari in produzione.
Perché la connessione a PostgreSQL è il primo collo di bottiglia della tua app?
PostgreSQL gestisce migliaia di connessioni ma non le regala. Ogni connessione aperta consuma memoria e processi. Se il tuo server Node apre una connessione per ogni richiesta, stai costruendo una coda davanti al database. Il risultato? Latenza che cresce e utenti che abbandonano il carrello.
La soluzione è un pool di connessioni. Il driver pg mette a disposizione pg.Pool, che riutilizza le connessioni esistenti invece di crearne di nuove. È la prima cosa che controlliamo quando un cliente ci dice che il sito "rallenta nelle ore di punta".
Sponsored Protocol
Come configurare un pool di connessioni con pg
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST,
port: 5432,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 10, // numero massimo di client nel pool
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
module.exports = pool;Questo è il minimo sindacale. Senza pool, ogni richiesta apre e chiude una connessione: un costo enorme. Con il pool, le connessioni vengono riutilizzate e il database respira.
Checklist per la configurazione:
- Usa sempre
pg.Poolin produzione, mainew Client()per ogni query. - Imposta
connectionTimeoutMillisper non lasciare richieste appese. - Metti le credenziali in variabili d'ambiente, mai nel codice.
Quando usare il driver pg diretto e quando passare a Knex?
Il driver pg è il livello più basso: scrivi SQL puro e lo invii. Knex è un query builder: costruisci query in JavaScript e lui le traduce in SQL. La domanda non è "quale è migliore", ma "quale ti fa perdere meno tempo e soldi".
Se il tuo progetto ha query semplici e poche tabelle, il driver diretto è sufficiente. Se hai join complessi, migrazioni da gestire e vuoi evitare errori di sintassi SQL, Knex ti dà una rete di sicurezza. Noi usiamo Knex quando il modello dati cresce e le query diventano difficili da mantenere a mano.
Sponsored Protocol
Un esempio concreto di query con pg
const pool = require('./pool');
async function getOrdersByUser(userId) {
const result = await pool.query(
'SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC',
[userId]
);
return result.rows;
}Nota i $1 per i parametri: non concatenare mai valori direttamente nella stringa SQL. È il modo più rapido per aprire la porta a SQL injection, e lo vediamo più spesso di quanto vorremmo.
La stessa query con Knex
const knex = require('./knex');
async function getOrdersByUser(userId) {
return knex('orders')
.select('*')
.where('user_id', userId)
.orderBy('created_at', 'desc');
}Knex gestisce i parametri automaticamente e il codice è più leggibile. Ma attenzione: Knex non ti salva da query inefficienti. Se scrivi un where che ignora gli indici, il risultato è lo stesso disastro.
Come gestire le migrazioni del database senza andare in produzione con il cerino acceso?
Le migrazioni sono il modo per versionare lo schema del database. Senza, ogni modifica alla struttura è un salto nel buio. Knex ha un sistema di migrazioni integrato che funziona bene: crei un file, definisci up e down, e il gioco è fatto.
Sponsored Protocol
Creare e applicare una migrazione con Knex
npx knex migrate:make create_orders_tableexports.up = function(knex) {
return knex.schema.createTable('orders', (table) => {
table.increments('id');
table.integer('user_id').unsigned().notNullable();
table.decimal('total', 10, 2).notNullable();
table.timestamps(true, true);
table.foreign('user_id').references('users.id');
});
};
exports.down = function(knex) {
return knex.schema.dropTable('orders');
};Con il driver pg puro, dovresti scrivere gli script SQL a mano e gestire l'ordine di esecuzione. Knex ti dà una struttura chiara e la possibilità di rollback. Per un progetto che cresce, è un investimento che si ripaga.
Errore comune: applicare migrazioni in produzione senza testarle prima in locale. Il risultato è un database rotto e clienti che non possono ordinare. Testa sempre in un ambiente di staging.
Quali errori di performance ti fanno perdere clienti e come evitarli?
La query perfetta non esiste se gli indici non ci sono. PostgreSQL ha un ottimizzatore, ma senza indici adatti, ogni SELECT diventa una scansione completa della tabella. I dati crescono e il tempo di risposta sale in modo lineare.
Come individuare le query lente
Abilita il log delle query lente in PostgreSQL e usa EXPLAIN ANALYZE per capire cosa succede. Noi lo facciamo sempre prima di toccare qualsiasi cosa.
Sponsored Protocol
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 123;Se vedi Seq Scan su una tabella grande, è il momento di aggiungere un indice.
CREATE INDEX idx_orders_user_id ON orders(user_id);Questo singolo comando può ridurre il tempo di risposta da secondi a millisecondi. Un cliente e-commerce con migliaia di ordini lo sente subito.
Checklist per la performance:
- Usa
EXPLAIN ANALYZEsu ogni query che impiega più di 100ms. - Aggiungi indici sulle colonne usate in
WHERE,JOINeORDER BY. - Non usare
SELECT *in produzione: seleziona solo le colonne che servono.
Come proteggere i dati e le credenziali in un'app Node.js con PostgreSQL?
La sicurezza è sistematicamente sottovalutata nelle PMI italiane. Credenziali in chiaro nel codice, database esposti su internet, nessun backup. Lo vediamo ogni giorno. Con pg e Knex puoi fare la differenza con poche accortezze.
Proteggere le credenziali con variabili d'ambiente
// .env
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DB_USER=app_user
DB_PASSWORD=una_password_forte// config.js
require('dotenv').config();
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
});Mai committare il file .env su GitHub. Aggiungilo al .gitignore e usa un gestore di segreti per la produzione.
Sponsored Protocol
Inoltre, limita i permessi dell'utente del database: se l'app deve solo leggere e scrivere su alcune tabelle, non concedere i privilegi di amministratore. Un attacco SQL injection con un utente limitato fa molto meno danno.
Cosa fare adesso per un backend Node.js con PostgreSQL che regge il traffico
Non serve riscrivere tutto. Basta partire dai punti che bruciano di più.
Azioni immediate:
- Configura
pg.Poolcon parametri di timeout e max connessioni adeguati al tuo traffico. - Sostituisci le query con parametri concatenati con prepared statements o Knex.
- Analizza le query lente con
EXPLAIN ANALYZEe aggiungi gli indici mancanti. - Sposta le credenziali in variabili d'ambiente e limita i permessi dell'utente del database.
- Se il progetto cresce, adotta Knex per le migrazioni e la gestione dello schema.
Il database non deve essere il collo di bottiglia della tua app. Con le giuste pratiche, Node.js e PostgreSQL formano una coppia che regge il traffico e cresce con il tuo business. Se vuoi approfondire come scegliere Node.js per il tuo prossimo progetto, leggi la nostra guida principale su Node.js per il backend.