Вступление
В этой статье мы обсудим, как интегрировать PostgreSQL с Node.js.
Чтобы лучше следовать этой статье, мы рекомендуем вам иметь опыт использования Node.js и операторов SQL. В этой статье мы будем использовать простой синтаксис javascript ES6.
Есть несколько разных клиентов, которые вы можете использовать для
интеграции PostgreSQL с Node.js. В этой статье мы будем использовать
модуль node-postgres
Это популярный и развитый модуль по сравнению с
другими клиентами PostgreSQL.
Кроме того, вы также можете использовать PostgreSQL с ORM, например Sequelize . Но мы не будем использовать такой ORM-модуль в этой статье. Вместо этого мы будем использовать простые SQL-запросы, которые затем можно будет использовать для более сложных взаимодействий с базой данных.
PostgreSQL
PostgreSQL - популярная база данных SQL. Она активно разрабатывалась последние 30 с лишним лет и считается одной из самых передовых реляционных баз данных. PostgreSQL также прост в изучении и настройке по сравнению с другими доступными реляционными базами данных. Из-за того, что он бесплатный и имеет открытый исходный код, это популярный выбор среди стартапов.
PostgreSQL - это кроссплатформенная база данных, работающая во всех основных операционных системах. Однако конфигурация и доступ / создание базы данных немного различаются в зависимости от операционных систем.
В этой статье мы будем использовать Ubuntu 18.04, популярную платформу Linux, которая по умолчанию включает PostgreSQL. Некоторые шаги могут немного отличаться, если вы используете другую операционную систему.
Конфигурация проекта
Начнем с простого пустого проекта Node.js с настройками по умолчанию:
$ npm init -y
Затем давайте используем npm
для установки node-postgres
, который
будет использоваться для подключения и взаимодействия с Postgres:
$ npm install --save pg
Реализация операций CRUD
После начальной загрузки нашего проекта давайте продолжим и настроим базу данных. После этого мы напишем некоторые базовые функции CRUD.
Конфигурация базы данных
Как и в случае со всеми реляционными базами данных, мы начнем с создания базы данных и подключения к ней. Для этого вы можете использовать интерфейс командной строки или клиент с графическим интерфейсом. Поскольку настроить через интерфейс командной строки очень просто, мы и будем этим заниматься.
Для Ubuntu команда psql
по умолчанию войдет в интерфейс командной
строки. PostgreSQL создаст пользователя с именем postgres
для доступа
к базе данных на платформах на базе Linux. Таким образом, мы можем
использовать следующую команду для входа в систему как пользователь
postgres
$ sudo -i -u postgres
Затем войдите в CLI, запустив:
$ psql
Вы должны увидеть командную оболочку, подобную этой:
{.ezlazyload}
Чтобы просмотреть текущие базы данных, воспользуемся командой \list
или \l
:
{.ezlazyload}
А теперь давайте создадим наш собственный с помощью SQL-запроса:
CREATE DATABASE testdb;
Выполняя эту команду, мы создаем testdb
данных testdb и приветствуем
вывод, подтверждающий нашу команду:
CREATE DATABASE
Поскольку база данных создана, теперь мы можем получить к ней доступ.
Хотя PostgreSQL создает postgres
умолчанию, пароль по умолчанию не
установлен. Если вы хотите установить пароль (а не оставлять его
пустым), используйте команду \password
:
{.ezlazyload}
Установив пароль, мы готовы использовать базу данных в нашем проекте.
Подключение к базе данных
У вас есть два варианта подключения к серверу PostgreSQL с помощью
модуля node-postgres
Один из вариантов - использовать одного клиента.
Другой метод - использовать пул соединений. Однако, если ваше приложение
очень часто использует базу данных, пул будет лучшим вариантом, чем
использование одного клиента.
Подключиться к базе данных с помощью node-postgres
можно двумя
способами - с помощью одного клиента и с помощью пула подключений .
Позже в этой статье мы рассмотрим, как использовать пул соединений для подключения к базе данных. А пока давайте для краткости и простоты подключимся к базе данных с помощью одного клиента:
const { Client } = require('pg');
const client = new Client({
user: 'postgres',
host: 'localhost',
database: 'testdb',
password: '1234abcd',
port: 5432,
});
client.connect();
Здесь мы вручную настроили параметры. Однако вы можете подключиться к базе данных, не передавая ни один из них:
const { Client } = require('pg');
const client = new Client();
client.connect();
Но опять же, Node нужно знать, как подключиться к базе данных, поэтому мы предоставим их через переменные среды:
PGUSER=dbuser
PGHOST=database.server.com
PGPASSWORD=secretpassword
PGDATABASE=mydb
PGPORT=3211
Если вы не настраивали их самостоятельно, модуль будет использовать значения по умолчанию:
PGHOST='localhost'
PGUSER=process.env.USER
PGDATABASE=process.env.USER
PGPASSWORD=null
PGPORT=5432
В Linux process.env.USER
будет содержать значение для текущего
пользователя, который вошел в систему.
Создание таблиц
Подготовив базу данных для вставки данных, давайте создадим несколько
таблиц для хранения наших данных. Как и во всех базах данных на основе
SQL, мы будем использовать запрос CREATE TABLE
CREATE TABLE [table_name] (
[column1] [datatype],
[column2] [datatype],
[column3] [datatype],
....
);
Таблица состоит из столбцов , и каждый столбец имеет тип данных.
Например, firstName
будет иметь varchar
в качестве типа данных,
который представляет строку переменного размера.
Если вы хотите узнать больше о поддерживаемых типах данных, вдокументации PostgreSQL они перечислены.
При этом мы можем использовать этот запрос для создания таблицы в базе данных:
const query = `
CREATE TABLE users (
email varchar,
firstName varchar,
lastName varchar,
age int
);
`;
Чтобы фактически запустить этот запрос к базе данных, мы используем
функцию query()
client
объекта, который мы настроили ранее:
client.query(query, (err, res) => {
if (err) {
console.error(err);
return;
}
console.log('Table is successfully created');
client.end();
});
Примечание. Не забудьте end()
ваше соединение с клиентом после
выполнения запроса.
Запуск этого кода создаст нашу таблицу и распечатает:
Table is successfully created
Этого также можно добиться с помощью обещаний и async/await
.
Поскольку вызов базы данных может завершиться ошибкой, имеет смысл
использовать обещания:
client
.query(query)
.then(res => {
console.log('Table is successfully created');
})
.catch(err => {
console.error(err);
})
.finally(() => {
client.end();
});
Как вы можете видеть в примере, мы можем использовать блок finally,
чтобы закрыть соединение с базой данных. Таким образом, даже если запрос
выдал err
, соединение будет закрыто.
В качестве альтернативы мы также можем использовать синтаксис
async/await
try {
const res = await client.query(query);
console.log('Table is successfully created');
} catch (err) {
console.log(err.stack);
} finally {
client.close();
}
Все эти подходы должны дать один и тот же результат:
Table is successfully created
Чтобы убедиться в этом, давайте воспользуемся psql
для проверки БД.
Откройте терминал, запустите оболочку с помощью psql
и выберите базу
данных с помощью команды \c [database]
. \c
- это сокращение от
\connect
:
\c testdb
testdb
список таблиц в базе данных testdb, выполнив команду \dt
:
{.ezlazyload}
Вы также можете запросить конкретные таблицы, указав их имена:
testdb=# \dt FOO
Этот запрос отобразит таблицу с именем FOO
.
Создание / вставка данных
Мы можем использовать оператор SQL INSERT INTO
для вставки данных в
таблицу:
INSERT INTO [table_name] ([column1], [column2], [column3], ...)
VALUES ([value1], [value2], [value3], ...);
Чтобы сделать этот запрос конкретным, давайте вставим наши собственные значения и построим запрос:
const query = `
INSERT INTO users (email, firstName, lastName, age)
VALUES (' [email protected] ', 'john', 'doe', 21)
`;
И наконец, давайте запустим запрос к базе данных:
client.query(query, (err, res) => {
if (err) {
console.error(err);
return;
}
console.log('Data insert successful');
client.end();
});
Примечание. Как и в прошлый раз, эта функция может быть написана с
использованием синтаксиса async/await
Эти дополнительные примеры
опущены для краткости.
Запуск этого кода вставит пользователя в нашу базу данных и распечатает:
Data insert successful
Чтобы убедиться в этом, в нашей testdb
данных SELECT
:
SELECT * from users;
Очевидно, мы видим, что пользователь действительно был успешно создан:
{.ezlazyload}
Получение / выбор данных
Для получения данных из базы данных используется SELECT
:
SELECT [column1], [column2], ...
FROM [table_name]
WHERE [condition];
Вы можете выбрать определенные столбцы, указав их, или выбрать все поля
таблицы, используя подстановочный знак *
При желании вы можете
проявить творческий подход к большему количеству условных выражений,
WHERE
Здесь мы выбираем все строки и все столбцы из базы данных users
const query = `
SELECT *
FROM users
`;
Теперь, чтобы запустить этот запрос к базе данных, мы снова
воспользуемся client
:
client.query(query, (err, res) => {
if (err) {
console.error(err);
return;
}
for (let row of res.rows) {
console.log(row);
}
client.end();
});
Запуск этого кода даст:
{
email: ' [email protected] ',
firstname: 'john',
lastname: 'doe',
age: 21
}
{
email: ' [email protected] ',
firstname: 'anna',
lastname: 'dias',
age: 35
}
Этот запрос возвращает всех пользователей, добавленных в базу данных. Вы также можете фильтровать пользователей по их полям.
Например, если бы мы хотели вернуть всех пользователей младше 30 лет, мы
бы добавили предложение WHERE
:
const query = `
SELECT *
FROM users
WHERE age<30
`;
А затем мы запускали его в базе данных:
client.query(query, (err, res) => {
if (err) {
console.error(err);
return;
}
for (let row of res.rows) {
console.log(row);
}
client.end();
});
Запуск этого кода даст:
{
email: ' [email protected] ',
firstname: 'john',
lastname: 'doe',
age: 21
}
Обновление данных
Чтобы обновить уже существующие данные, мы можем использовать оператор
UPDATE
UPDATE [table_name]
SET [column1] = [value1], [column2] = [value2], ...
WHERE [condition];
Вы можете установить каждое обновленное значение для каждого столбца с
помощью ключевого слова SET
После WHERE
вы можете определить
условие, при котором записи должны быть обновлены.
Давайте заполним наш запрос:
const query = `
UPDATE users
SET age = 22
WHERE email = ' [email protected] '
`;
Теперь давайте запустим запрос к базе данных:
client.query(query, (err, res) => {
if (err) {
console.error(err);
return;
}
if (err) {
console.error(err);
return;
}
console.log('Data update successful');
client.end();
});
Запуск этого фрагмента кода обновит записи, удовлетворяющие WHERE
, и
распечатает:
Data update successful
Для проверки давайте проверим нашу базу данных:
{.ezlazyload}
Удаление данных
Наконец, чтобы удалить данные, мы можем использовать оператор DELETE
DELETE FROM [table_name]
WHERE [condition];
Будьте осторожны с этим утверждением, так как вы можете случайно удалить больше, чем хотите.
Давайте заполним наш запрос:
const query = `
DELETE FROM users
WHERE email = ' [email protected] '
`;
И, наконец, запустим его в базе данных:
client.query(query, (err, res) => {
if (err) {
console.error(err);
return;
}
if (err) {
console.error(err);
return;
}
console.log('Data delete successful');
client.end();
});
Запуск этого кода удалит запись, удовлетворяющую WHERE
, и
распечатает:
Data delete successful
Для проверки взглянем на базу данных:
{.ezlazyload}
Объединение
Если ваше приложение часто использует базу данных, использование одного клиентского подключения к базе данных, скорее всего, замедлит работу приложения, когда у вас будет много пользовательских запросов. Самый простой и удобный способ решить эту проблему - использовать пул соединений.
Обычно, когда новый клиент подключается к базе данных, процесс установления соединения и аутентификации занимает около 20-30 миллисекунд. Это важно, когда вы выполняете больше запросов, что приводит к задержке в несколько секунд, что, вероятно, приведет к неудовлетворительному опыту конечного пользователя.
Кроме того, сервер PostgreSQL может одновременно обрабатывать только ограниченное количество клиентов, которое будет зависеть от памяти вашего сервера. Таким образом, если за секунду выполняется 100 запросов, это ограничение может привести к сбою вашего сервера.
Кроме того, клиент может обрабатывать только один запрос за раз для одного соединения, что еще больше замедляет работу.
В такой ситуации вы можете использовать pg-pool
чтобы решить эту
проблему.
Создание пула
Сначала импортируйте класс Pool
из модуля pg
const { Pool } = require('pg');
Затем давайте создадим новый объект пула:
const pool = new Pool({
user: 'postgres',
host: 'localhost',
database: 'testdb',
password: '1234abcd',
port: 5432,
});
Если вы не настроите имя пользователя, хост и другие свойства, вам придется определить для них переменные среды в файле конфигурации. Это почти то же самое, что и при настройке одного клиента.
Затем давайте определим обработчик ошибок для пула. Если есть какие-либо ошибки, выбрасываемые из пула, обратный вызов в этом событии будет запущен:
pool.on('error', (err, client) => {
console.error('Error:', err);
});
Это покрывает нас в случае сетевой ошибки.
Затем, используя pool
, мы подключаемся к базе данных и используем
client
в этом пуле для выполнения запроса:
const query = `
SELECT *
FROM users
`;
pool.connect((err, client, done) => {
if (err) throw err;
client.query(query, (err, res) => {
done();
if (err) {
console.log(err.stack);
} else {
for (let row of res.rows) {
console.log(row);
}
}
});
});
Это должно дать:
{
email: ' [email protected] ',
firstname: 'john',
lastname: 'doe',
age: 21
}
{
email: ' [email protected] ',
firstname: 'anna',
lastname: 'dias',
age: 35
}
Опять же, в этом случае имеет смысл использовать обещания:
pool.connect()
.then((client) => {
client.query(query)
.then(res => {
for (let row of res.rows) {
console.log(row);
}
})
.catch(err => {
console.error(err);
});
})
.catch(err => {
console.error(err);
});
Или даже синтаксис async/await
(async () => {
try {
const client = await pool.connect();
const res = await client.query(query);
for (let row of res.rows) {
console.log(row);
}
} catch (err) {
console.error(err);
}
})();
Использование курсора для чтения больших запросов
Обычно данные, полученные в результате запроса, загружаются прямо в память. Чем больше набор данных, тем выше будет использование памяти.
Поэтому, когда вы пытаетесь запросить большой набор данных, который может содержать, как правило, тысячи записей, загружать все это в память крайне неэффективно, а зачастую это просто невозможно. Курсор может помочь вам в подобной ситуации, извлекая ограниченное количество записей за раз.
В некотором смысле использование курсора похоже на потоковую передачу
данных, поскольку вы будете обращаться к ним последовательно небольшими
блоками. Чтобы использовать курсор, мы должны сначала pg-cursor
$ npm install --save pg pg-cursor
Мы передадим new Cursor
в функцию query()
cursor
не получит
никакой информации, пока мы не укажем ограничение с помощью метода
read()
const { Pool } = require('pg');
const Cursor = require('pg-cursor');
const pool = new Pool({
user: 'postgres',
host: 'localhost',
database: 'testdb',
password: '1234abcd',
port: 5432,
});
(async () => {
const client = await pool.connect();
const query = 'SELECT * FROM users';
const cursor = await client.query(new Cursor(query));
cursor.read(1, (err, rows) => {
console.log('We got the first row set');
console.log(rows);
cursor.read(1, (err, rows) => {
console.log('This is the next row set');
console.log(rows);
});
});
})();
Метод cursor
read()
позволяет нам определить, сколько строк мы хотим
получить из текущего экземпляра cursor
В этом примере для простоты мы
ограничили строки для одной записи. После этого мы прочитали еще один
набор строк.
Если вы достигли конца строк в базе данных, rows
будет иметь длину 0
.
Заключение
PostgreSQL - действительно популярная бесплатная реляционная база данных
с открытым исходным кодом. Модуль node-postgres
- это широко
используемый и развитый модуль, который связывает Node.js с PostgreSQL.
В этой статье мы создали базу данных PostgreSQL и разработали базовые функции CRUD с помощью простого скрипта Node.js. Затем мы изучили поддержку пула и использование курсоров для ограничения извлекаемых данных.
Как всегда, исходный код доступен на GitHub .