PostgreSQL: Node.js (pg) — Pool, параметризованные запросы, JSONB
Подключение к PostgreSQL из Node.js через пакет pg: Pool, query с $1 $2, запросы по JSONB. Копируй — вставляй — работает. По документации node-postgres.
Как использовать
- Установи: npm i pg. Для TypeScript: npm i -D @types/pg.
- Создай Pool с host, database, user, password (лучше из process.env). Все запросы через pool.query(sql, [params]).
- Всегда используй плейсхолдеры $1, $2 — не подставляй значения в строку (защита от SQL-инъекций).
Подключение к PostgreSQL из Node.js делают через клиент node-postgres (pg). Для приложения нужен пул соединений (Pool) и параметризованные запросы. Проблема: подстановка значений в строку запроса ведёт к SQL-инъекциям; без пула каждое обращение создаёт новое соединение. Симптомы: уязвимость при пользовательском вводе, исчерпание соединений, ошибки «too many clients». Ниже — настройка Pool, запросы с $1, $2, работа с JSONB из Node по документации pg; проверка и типичные ошибки.
Решение
Подключение к PostgreSQL из Node.js через pg: Pool, параметризованные запросы, примеры по JSONB. По Pooling, Queries.
Установка и Pool
npm i pg
npm i -D @types/pg # TypeScript
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
port: 5432,
database: 'myapp',
user: 'app_user',
password: process.env.DB_PASSWORD,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
module.exports = { pool };
Параметризованные запросы
Параметры — вторым аргументом; в запросе плейсхолдеры $1, $2. Подстановка на стороне PostgreSQL — защита от инъекций.
const { pool } = require('./pool');
const res = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
const user = res.rows[0];
const insert = await pool.query(
'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *',
['user@example.com', 'Alice']
);
const newUser = insert.rows[0];
await pool.query('UPDATE users SET name = $1 WHERE id = $2', ['Bob', userId]);
Запросы по JSONB
Оператор @> — передаём JSON как параметр:
const products = await pool.query(
'SELECT * FROM products WHERE attrs @> $1',
[JSON.stringify({ color: 'red' })]
);
const list = await pool.query(
'SELECT * FROM products WHERE attrs @> $1',
[JSON.stringify({ brand: 'Apple', color: 'black' })]
);
Проверка ключа и извлечение поля:
const byKey = await pool.query(
"SELECT * FROM products WHERE attrs ? $1",
['brand']
);
const withBrand = await pool.query(
"SELECT id, name, attrs->>'brand' AS brand FROM products"
);
Готовый пример
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.PGHOST || 'localhost',
database: process.env.PGDATABASE || 'myapp',
user: process.env.PGUSER || 'app_user',
password: process.env.PGPASSWORD,
});
async function main() {
const user = await pool.query('SELECT * FROM users WHERE id = $1', [1]);
console.log(user.rows[0]);
const products = await pool.query(
'SELECT * FROM products WHERE attrs @> $1',
[JSON.stringify({ color: 'red' })]
);
console.log(products.rows);
await pool.end();
}
main().catch((err) => { console.error(err); process.exit(1); });
Проверка
-
Подключение — запустите скрипт с корректными host, database, user, password. Ожидаем вывод без ошибки подключения. При неверных данных — сообщение об ошибке (ECONNREFUSED, authentication failed и т.д.).
-
Результат запроса — res.rows — массив строк; res.rows[0] — первая строка. res.rowCount — количество затронутых строк (для INSERT/UPDATE/DELETE). Проверьте, что данные совпадают с тем, что в БД.
-
Закрытие пула — в скриптах и тестах после всех запросов вызывайте
await pool.end(), иначе процесс не завершится (пул держит соединения). В долгоживущем приложении (сервер) pool обычно не закрывают. -
JSONB-параметр — для
@>передавайте сериализованный JSON:JSON.stringify({ key: value }). Не подставляйте строку в запрос вручную — только через $1.
Типичные ошибки
- Подстановка в строку — никогда не делайте
query('SELECT * FROM users WHERE id = ' + userId). Всегда используйте $1, $2 и массив параметров. Иначе SQL-инъекция. - Не закрывать pool в скриптах — в разовых скриптах и тестах без pool.end() процесс может висеть. В сервере (Express, Fastify и т.д.) пул обычно один на приложение и не закрывается до остановки.
- Один Pool на приложение — создавайте один экземпляр Pool и переиспользуйте его. Не создавайте новый Pool на каждый запрос — это исчерпает лимит соединений PostgreSQL (max_connections).
- Пароль и секреты — не храните пароль в коде. Используйте process.env.PGPASSWORD или переменные окружения (PGHOST, PGDATABASE, PGUSER, PGPASSWORD).
Где применять
- Prod / dev: API, воркеры, скрипты миграций и админки на Node.js, подключающиеся к PostgreSQL. Для запросов по JSONB см. JSONB и GIN-индекс.
- Совместно с EXPLAIN — при отладке медленных запросов выполняйте те же запросы из приложения и проверяйте план в psql (EXPLAIN ANALYZE).
Связанные сниппеты: JSONB и GIN-индекс, EXPLAIN ANALYZE.
Документация: node-postgres, Pooling, Queries.