FullStackJS Camp
Módulo 7·practica·6h
Objetivos de aprendizaje
  • Configurar el Pool con parámetros avanzados de rendimiento
  • Usar pool.connect() / client.release() para controlar el ciclo de vida de la conexión
  • Escribir consultas parametrizadas con $1, $2 para prevenir SQL injection
  • Entender rowMode "array" y cuándo es útil
  • Realizar INSERT con RETURNING * para obtener el registro creado

Pool avanzado, rowMode y consultas parametrizadas

Configuración avanzada del Pool

El Pool acepta parámetros de ajuste de rendimiento que son críticos para producción:

javascript
// db/db.js
import pg from "pg";
import dotenv from "dotenv";

const { Pool } = pg;
dotenv.config();

export const pool = new Pool({
  user:     process.env.DB_USER,
  host:     process.env.DB_HOST,
  database: process.env.DB_DATABASE,
  password: process.env.DB_PASSWORD,
  port:     Number(process.env.DB_PORT) || 5432,

  max:                    20,    // máximo de conexiones simultáneas
  min:                    2,     // mínimo de conexiones en reposo
  connectionTimeoutMillis: 2000, // ms máx para obtener una conexión
  idleTimeoutMillis:      30000, // ms antes de destruir una conexión inactiva
});

Variables de entorno

bash
# .env
DB_USER=postgres
DB_HOST=localhost
DB_DATABASE=clientes
DB_PASSWORD=postgres
DB_PORT=5432

¿Qué valores usar?

ParámetroDesarrolloProducción
max5–1020–50 (según RAM)
min0–22–5
connectionTimeoutMillis20005000
idleTimeoutMillis3000060000

pool.query() vs pool.connect()

Hay dos formas de ejecutar consultas:

code
pool.query(sql)
  └─ Obtiene conexión → ejecuta → libera automáticamente
  ✅ Simple para consultas únicas

pool.connect() → client → client.query() → client.release()
  └─ Tú controlas manualmente el ciclo de vida
  ✅ Necesario para transacciones (BEGIN/COMMIT/ROLLBACK)

Patrón con pool.connect() / client.release()

javascript
import { pool } from "./db/db.js";

const insertarUsuario = async () => {
  let client;
  try {
    client = await pool.connect(); // 1. Obtener cliente del pool
    const resultado = await client.query(
      "INSERT INTO usuarios (nombre, email) VALUES ($1, $2) RETURNING *",
      ["Ana García", "ana@demo.com"]
    );
    console.log("Usuario creado:", resultado.rows[0]);
  } catch (error) {
    console.error("Error:", error.stack);
  } finally {
    if (client) client.release(); // 2. SIEMPRE devolver al pool
  }
};

insertarUsuario();

Consultas parametrizadas

Las consultas parametrizadas son la defensa principal contra SQL injection. En pg, los parámetros se escriben como $1, $2, etc. (índice basado en 1):

javascript
// ❌ NUNCA hagas esto — SQL injection
const sql = `SELECT * FROM usuarios WHERE email = '${email}'`;

// ✅ Siempre usa parámetros
const sql = "SELECT * FROM usuarios WHERE email = $1";
const result = await pool.query(sql, [email]);

Objeto de consulta (SQLQuery object)

Para consultas complejas o reutilizables, puedes encapsular la consulta en un objeto:

javascript
const SQLQuery = {
  text:   "INSERT INTO usuarios (first_name, last_name, email, saldo) VALUES ($1, $2, $3, $4) RETURNING *",
  values: ["Joselyn", "Riquelme", "joselyn@example.com", 200000],
};

const result = await client.query(SQLQuery);
console.log(result.rows); // → [{ id: 3, first_name: 'Joselyn', ... }]

Tipos de datos y $n

pg convierte automáticamente entre tipos JavaScript y PostgreSQL:

JavaScriptPostgreSQL
stringVARCHAR, TEXT, UUID
numberINT, NUMERIC, FLOAT
DateTIMESTAMP, DATE
booleanBOOLEAN
nullNULL
object/arrayJSON, JSONB

rowMode: "array"

Por defecto, pg devuelve cada fila como un objeto { columna: valor }. Con rowMode: "array", devuelve arrays de valores:

javascript
const query = {
  rowMode: "array",
  text:    "SELECT id, nombre, email FROM usuarios WHERE id = $1",
  values:  [1],
};

const result = await client.query(query);

// Con rowMode por defecto (objeto):
// result.rows → [{ id: 1, nombre: 'Ana', email: 'ana@demo.com' }]

// Con rowMode: "array":
// result.rows → [[1, 'Ana', 'ana@demo.com']]
// result.fields → [{ name: 'id' }, { name: 'nombre' }, { name: 'email' }]

INSERT con RETURNING *

RETURNING * hace que PostgreSQL devuelva el registro después de insertarlo o actualizarlo — sin necesidad de hacer un segundo SELECT:

javascript
// INSERT y obtener el registro creado en una sola operación
const nuevaFila = async (nombre, email, saldo) => {
  let client;
  try {
    client = await pool.connect();

    const { rows } = await client.query(
      "INSERT INTO usuarios (nombre, email, saldo) VALUES ($1, $2, $3) RETURNING *",
      [nombre, email, saldo]
    );

    return rows[0]; // El registro recién insertado con su id asignado
  } finally {
    if (client) client.release();
  }
};

const usuario = await nuevaFila("Pedro López", "pedro@demo.com", 150000);
console.log(usuario); // { id: 4, nombre: 'Pedro López', email: 'pedro@demo.com', saldo: 150000 }

Resumen del flujo

code
.env
  └─ DB_USER, DB_HOST, DB_DATABASE, DB_PASSWORD, DB_PORT

pool = new Pool({ ...config })
  ├─ pool.query(sql, [params])          ← consultas simples
  └─ pool.connect() → client
        ├─ client.query(sql, [params])  ← transacciones o múltiples queries
        └─ client.release()             ← SIEMPRE en finally