Зачем JavaScript дружит с PostgreSQL?
Если вы пишете на JavaScript (особенно на Node.js) и до сих пор используете MySQL или, упаси боже, SQLite в продакшене, — вы теряете деньги и нервы. PostgreSQL — это не просто "еще одна база данных". Это швейцарский нож для данных, который умеет всё: от сложных JOIN до JSON-документов, полнотекстового поиска и гео-запросов. А связка JavaScript + PostgreSQL — это современный стандарт для веб-приложений, микросервисов и аналитики.
В этом руководстве мы разберем всё, что нужно знать Node.js-разработчику: от установки драйвера до транзакций, миграций и оптимизации. Никакой воды — только код и практика.
Шаг 1: Установка и настройка драйвера pg
Для работы с PostgreSQL из JavaScript используется пакет pg — официальный клиент. Устанавливается он за секунду:
npm install pg
Теперь создаем подключение. Есть два подхода: Client (одно соединение) и Pool (пул соединений). Для продакшена всегда используйте Pool — он управляет переиспользованием соединений и не кладет базу при нагрузке.
const { Pool } = require('pg');
const pool = new Pool({
user: 'your_user',
host: 'localhost',
database: 'your_db',
password: 'your_password',
port: 5432,
max: 20, // макс. соединений в пуле
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// Тест подключения
pool.query('SELECT NOW()')
.then(res => console.log('✅ Подключено:', res.rows[0].now))
.catch(err => console.error('❌ Ошибка:', err));
Важно: Никогда не хардкодьте пароли. Используйте переменные окружения (process.env.DATABASE_URL) или менеджер секретов.
Шаг 2: CRUD — основа основ
Любое приложение начинается с CRUD (Create, Read, Update, Delete). Разберем каждую операцию на JavaScript с PostgreSQL.
Создание таблицы (DDL)
const createTable = async () => {
const query = `
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
age INTEGER CHECK (age > 0),
created_at TIMESTAMP DEFAULT NOW()
);
`;
await pool.query(query);
console.log('✅ Таблица users создана');
};
createTable();
INSERT — добавление данных
const addUser = async (name, email, age) => {
// Используем параметризованный запрос — защита от SQL-инъекций!
const query = `
INSERT INTO users (name, email, age)
VALUES ($1, $2, $3)
RETURNING *;
`;
const values = [name, email, age];
const res = await pool.query(query, values);
console.log('✅ Добавлен пользователь:', res.rows[0]);
return res.rows[0];
};
// Пример вызова
addUser('Анна Петрова', 'anna@example.com', 28);
SELECT — чтение с фильтрацией
const getUsers = async (minAge = 0) => {
const query = 'SELECT * FROM users WHERE age > $1 ORDER BY created_at DESC';
const res = await pool.query(query, [minAge]);
console.log(`📊 Найдено пользователей: ${res.rowCount}`);
return res.rows;
};
getUsers(25).then(users => console.log(users));
UPDATE — обновление записи
const updateUserEmail = async (id, newEmail) => {
const query = `
UPDATE users
SET email = $2, updated_at = NOW()
WHERE id = $1
RETURNING *;
`;
const res = await pool.query(query, [id, newEmail]);
if (res.rowCount === 0) {
console.log('⚠️ Пользователь не найден');
return null;
}
console.log('✅ Email обновлен:', res.rows[0]);
return res.rows[0];
};
DELETE — удаление
const deleteUser = async (id) => {
const query = 'DELETE FROM users WHERE id = $1 RETURNING *';
const res = await pool.query(query, [id]);
console.log(`🗑️ Удален пользователь: ${res.rowCount > 0 ? res.rows[0].name : 'не найден'}`);
};
Шаг 3: Продвинутые техники
Просто CRUD — это скучно. Давайте посмотрим, как JavaScript и PostgreSQL работают вместе в реальных сценариях.
Транзакции — спасаем данные
Когда нужно выполнить несколько запросов атомарно (всё или ничего), используем транзакции.
const transferMoney = async (fromId, toId, amount) => {
const client = await pool.connect(); // получаем клиент из пула
try {
await client.query('BEGIN'); // начинаем транзакцию
// Списание
await client.query(
'UPDATE accounts SET balance = balance - $1 WHERE id = $2 AND balance >= $1',
[amount, fromId]
);
// Зачисление
await client.query(
'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
[amount, toId]
);
await client.query('COMMIT'); // всё ок — фиксируем
console.log('✅ Перевод выполнен');
} catch (err) {
await client.query('ROLLBACK'); // ошибка — откат
console.error('❌ Ошибка перевода, откат:', err.message);
} finally {
client.release(); // обязательно возвращаем клиент в пул
}
};
Работа с JSON в PostgreSQL
PostgreSQL поддерживает JSONB — бинарный JSON с индексацией. Это идеально для гибких схем.
const addProduct = async (product) => {
const query = `
INSERT INTO products (name, metadata)
VALUES ($1, $2::jsonb)
RETURNING *;
`;
const metadata = JSON.stringify({
color: product.color,
weight: product.weight,
tags: product.tags || []
});
const res = await pool.query(query, [product.name, metadata]);
return res.rows[0];
};
// Поиск по JSON-полю
const findProductsByTag = async (tag) => {
const query = "SELECT * FROM products WHERE metadata->'tags' ? $1";
const res = await pool.query(query, [tag]);
return res.rows;
};
Миграции баз данных
Ручное создание таблиц — путь в ад. Используйте миграции. Популярные инструменты для Node.js и PostgreSQL: Knex.js или node-pg-migrate.
Пример миграции на Knex.js:
// Файл: migrations/20231001_create_users.js
exports.up = function(knex) {
return knex.schema.createTable('users', (table) => {
table.increments('id').primary();
table.string('name', 100).notNullable();
table.string('email').unique().notNullable();
table.integer('age').checkPositive();
table.timestamps(true, true);
});
};
exports.down = function(knex) {
return knex.schema.dropTableIfExists('users');
};
Запуск: npx knex migrate:latest