CRUD API на Node и PostgreSQL

Что такое CRUD и зачем это нужно
CRUD — это четыре базовые операции с данными: Create (создать), Read (читать), Update (обновить) и Delete (удалить). Эти операции лежат в основе большинства REST API, которые взаимодействуют с реляционными базами данных.
Краткое определение: CRUD — стандартный набор действий для управления записями в базе данных.
Требования
Перед началом убедитесь, что у вас установлены:
- Node.js.
- PostgreSQL (локально или удалённый сервер).
- Базовые знания Express.js.
Совет: используйте Node версии LTS и храните конфигурацию доступа к базе в переменных окружения (см. секцию безопасности).
Шаг 1 — Создаём Express сервер
Создайте каталог проекта и перейдите в него:
mkdir notes
cd notesИнициализируйте npm и установите зависимости:
npm init -y
npm install expressСоздайте файл index.js с базовой конфигурацией сервера:
const express = require("express");
const app = express();
app.use(express.urlencoded({
extended: true
}));
app.use(express.json());
app.listen(3000, () => console.log("Listening on port 3000"));После этого у вас будет работающий сервер на порту 3000.
Шаг 2 — Создаём базу данных PostgreSQL
В psql выполните команду для создания базы данных notedb:
CREATE DATABASE notedb;Проверьте список баз данных:
\lШаг 3 — Подключение Node к PostgreSQL
Установите клиент node-postgres:
npm install pgВ качестве хорошей практики храните подключение в отдельном файле.
Создайте db.js:
const { Client } = require("pg");
const { user, host, database, password, port } = require("./dbConfig");
const client = new Client({
user,
host,
database,
password,
port,
});
client.connect();
module.exports = client;Создайте файл dbConfig.js и укажите параметры подключения (замените плейсхолдеры на свои данные):
module.exports = {
user: "{dbUser}",
host: "{dbHost}",
database: "notedb",
password: "{dbPassword}",
port: 5432,
};Важно: в production используйте переменные окружения и не храните пароли в репозитории.
Шаг 4 — Создаём таблицу notes
Подключитесь к notedb:
\c notedbСоздайте таблицу:
CREATE TABLE notes (
ID SERIAL PRIMARY KEY,
note VARCHAR(255)
);Таблица содержит автоинкрементный первичный ключ ID и текстовое поле note.
Шаг 5 — Функции CRUD (helper.js)
Вынесем логику работы с БД в отдельный файл helper.js. Сначала импортируем соединение:
const client = require("./db");Функция для создания заметки:
const createNote = (req, res) => {
try {
const { note } = req.body;
if (!note) {
throw Error("Send note in request body");
}
client.query(
"INSERT INTO notes (note) VALUES ($1)",
[note],
(err, data) => {
res.status(201).json({
error: null,
message: "Created new note",
});
}
);
} catch (error) {
res.status(500).json({
error: error.message,
message: "Failed to create new note",
});
}
};Функция для получения всех заметок:
const getNotes = (req, res) => {
try {
client.query("SELECT * FROM notes", (err, data) => {
if (err) throw err;
res.status(200).json({
err: null,
notes: data.rows,
});
});
} catch (error) {
res.status(500).json({
err: error.message,
notes: null,
});
}
};Функция для получения заметки по ID:
const getNoteById = (req, res) => {
try {
const { id } = req.params;
client.query("SELECT * FROM notes WHERE id=$1", [id], (err, data) => {
if (err) throw err;
res.status(200).json({
err: null,
note: data.rows[0],
});
});
} catch (error) {
res.status(500).json({
err: err.message,
note: null,
});
}
};Функция для обновления заметки по ID:
const updateNoteById = (req, res) => {
try {
const { id } = req.params;
const { note } = req.body;
client.query(
"UPDATE notes SET note = $1 WHERE id = $2",
[note, id],
(err, data) => {
if (err) throw err;
res.status(201).json({
err: null,
message: "Updated note",
});
}
);
} catch (error) {
res.status(500).json({
err: error.message,
message: "Failed to update note",
});
}
};Функция для удаления заметки по ID:
const deleteNote = (req, res) => {
try {
const { id } = req.params;
client.query("DELETE FROM notes WHERE id=$1", [id], (err, data) => {
if (err) throw err;
res.status(200).json({
error: null,
message: "Note deleted",
});
});
} catch (error) {
res.status(500).json({
error: error.message,
message: "Failed to delete note",
});
}
};Экспортируем функции в конце helper.js:
module.exports = { createNote, getNotes, getNoteById, updateNoteById, deleteNote };Шаг 6 — Маршруты API (index.js)
Импортируйте helper и создайте маршруты:
const db = require("./helper");
app.get("/notes", db.getNotes);
app.get("/note/:id", db.getNoteById);
app.put("/note/:id", db.updateNoteById);
app.post("/note", db.createNote);
app.delete("/note/:id", db.deleteNote);Теперь у вас есть пять REST-эндпоинтов для управления заметками.
Проверка и отладка
Проверять можно через Postman, curl или тесты. Примеры curl-запросов:
Создать заметку:
curl -X POST -H "Content-Type: application/json" -d '{"note":"Моя заметка"}' http://localhost:3000/noteПолучить все заметки:
curl http://localhost:3000/notesПолучить заметку по id:
curl http://localhost:3000/note/1Обновить заметку:
curl -X PUT -H "Content-Type: application/json" -d '{"note":"Обновлённая заметка"}' http://localhost:3000/note/1Удалить заметку:
curl -X DELETE http://localhost:3000/note/1Лучшие практики и безопасное развёртывание
Important: никогда не храните пароли и строки подключения в репозитории.
Рекомендации:
- Используйте pg.Pool вместо Client для управления соединениями в продакшене.
- Храните конфигурацию в переменных окружения (process.env) или в секретном хранилище.
- Всегда используйте параметризованные запросы (как в примере с $1), чтобы предотвратить SQL-инъекции.
- Проверяйте входные данные: используйте валидацию схем (например Joi или express-validator).
- Ограничьте CORS и доступ к API по необходимости.
- Включите HTTPS на уровне прокси/балансировщика.
- Лимитируйте число запросов (rate limiting) и логируйте ошибочные запросы.
Пример конфигурации через переменные окружения (рекомендация для dbConfig.js):
module.exports = {
user: process.env.PG_USER,
host: process.env.PG_HOST,
database: process.env.PG_DATABASE,
password: process.env.PG_PASSWORD,
port: parseInt(process.env.PG_PORT, 10) || 5432,
};Когда этот подход не подходит
Контрпримеры/ограничения:
- Высоконагруженные приложения: для большого числа одновременных соединений нужна Pool и оптимизация запросов.
- Сложные доменные модели: удобнее использовать ORM (например TypeORM или Sequelize) или CQRS-подход.
- Транзакционная логика: используйте явные транзакции и тестируйте откаты.
Альтернативные подходы
- Использовать ORM (TypeORM, Sequelize) — упрощают работу с моделями и миграциями.
- GraphQL вместо REST — когда клиенту нужны гибкие запросы.
- Использовать миграции (Flyway, db-migrate или миграции в ORM) для управления схемой БД.
Чек-лист по ролям
Developer:
- Настроил .env и не закоммитил секреты.
- Использует Pool в продакшене.
- Написал юнит/интеграционные тесты для маршрутов.
DBA:
- Установил резервное копирование и точки восстановления.
- Настроил мониторинг и метрики производительности.
Tester:
- Проверил сценарии happy-path и негативные сценарии.
- Валидировал реакции на некорректные входные данные.
Критерии приёмки
- Все 5 эндпоинтов возвращают ожидаемые HTTP-коды и JSON-ответы.
- При некорректных входных данных API возвращает корректные ошибки (4xx/5xx).
- Данные сохраняются, обновляются и удаляются в базе данных как ожидается.
- Пароли и секреты не попали в репозиторий.
Тест-кейсы (минимум)
- Создание заметки
- Вход: валидный JSON {“note”:”текст”}
- Ожидание: 201 Created, сообщение об успешном создании.
- Получение списка
- Ожидание: 200 OK, массив заметок.
- Получение по несуществующему ID
- Ожидание: 200 OK, note: null или 404 с описанием (вариация зависит от реализации).
- Обновление note с пустым телом
- Ожидание: 500 или 400 с сообщением об ошибке валидации.
- Удаление
- Ожидание: 200 OK и подтверждение удаления.
Короткие подсказки и шпаргалка
- Используйте параметр $1, $2 и передаваемые массивы для безопасных запросов.
- В тестах создавайте отдельную тестовую БД или используйте транзакции с откатом.
- Для миграций используйте специальный инструмент, чтобы синхронизировать схему между окружениями.
Итог
Вы создали минимальный CRUD API на Express и PostgreSQL. Этот шаблон легко расширяется: добавьте аутентификацию, валидацию, миграции и мониторинг перед выпуском в продакшен. Начните с этого каркаса и улучшайте архитектуру по мере возрастания требований.
Notes: можно управлять базой из GUI через pgAdmin для удобства администрирования и обзора данных.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone