База данных SQLite
JSON-файл перестаёт справляться, когда статей становятся тысячи или два запроса пытаются записать файл одновременно. Тогда данные переносят в базу данных. SQLite — небольшая база, которая хранится в одном файле и не требует отдельного сервера. В Node.js 22.13 и новее она встроена: модуль node:sqlite. Язык запросов SQL здесь используется в самом простом виде.
Подключение и таблица
Класс DatabaseSync открывает файл базы (или создаёт его). Метод exec() выполняет SQL-команду:
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync('blog.db');
db.exec(`
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL DEFAULT ''
)
`);
console.log('Таблица posts готова');Рядом появится файл blog.db. CREATE TABLE IF NOT EXISTS создаёт таблицу только при первом запуске, а AUTOINCREMENT сам выдаёт номера статей. Вместо имени файла можно передать ':memory:' — база будет жить только в памяти.
Вставка
Запрос с данными готовят методом prepare(), а значения подставляют на место знаков ?:
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE posts (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, body TEXT)');
const insert = db.prepare('INSERT INTO posts (title, body) VALUES (?, ?)');
const result = insert.run('Привет, SQLite', 'Первая статья в базе');
console.log(result.lastInsertRowid); // 1run() возвращает объект с номером новой строки и числом изменённых строк (changes).
Выборка
all() возвращает массив всех найденных строк, get() — одну строку или undefined:
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE posts (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT)');
const insert = db.prepare('INSERT INTO posts (title) VALUES (?)');
insert.run('Привет, Node.js');
insert.run('Работа с файлами');
const posts = db.prepare('SELECT id, title FROM posts ORDER BY id').all();
for (const post of posts) console.log(post.id, post.title);
const post = db.prepare('SELECT title FROM posts WHERE id = ?').get(2);
console.log(post.title); // Работа с файламиВыведутся две строки со статьями, затем заголовок второй.
В проекте блога
Модуль src/posts.js можно переписать на базу, сохранив те же функции — сервер ничего не заметит:
// src/posts.js
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync('blog.db');
export async function loadPosts() {
return db.prepare('SELECT * FROM posts ORDER BY id DESC').all();
}
export async function findPost(id) {
return db.prepare('SELECT * FROM posts WHERE id = ?').get(id);
}Методы node:sqlite синхронные, а async оставлен, чтобы код сервера с await работал без изменений. Если ваша версия Node.js старше 22.13, используйте пакет better-sqlite3 — у него очень похожий интерфейс.
Частая ошибка
Нельзя вставлять данные от пользователя прямо в текст запроса:
// src/posts.js
// Опасно: db.prepare(`SELECT * FROM posts WHERE title = '${title}'`)Заголовок вида ' OR '1'='1 изменит смысл запроса — это SQL-инъекция. Всегда передавайте значения через ? и аргументы run(), get() или all(): база подставит их безопасно.