Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Подключение базы данных

Массив в памяти очищается при каждом перезапуске сервера. Чтобы статьи и комментарии сохранялись, нужна база данных. В курсе используется SQLite — вся база хранится в одном файле и не требует отдельного сервера, — а работает с ней пакет better-sqlite3. Запросы к базе пишутся на языке SQL.

Установка

npm install better-sqlite3

Пакет содержит скомпилированную часть, поэтому установка может занять минуту.

Плагин базы данных

Подключение к базе нужно всем маршрутам, поэтому его оформляют плагином с fastify-plugin. Создайте src/plugins/db.js:

// src/plugins/db.js
import fp from 'fastify-plugin';
import Database from 'better-sqlite3';
 
async function dbPlugin(app) {
  const db = new Database(app.config.DB_FILE);
  db.pragma('foreign_keys = ON');
 
  db.exec(`
    CREATE TABLE IF NOT EXISTS posts (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      title TEXT NOT NULL,
      body TEXT NOT NULL,
      created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
    CREATE TABLE IF NOT EXISTS comments (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
      author TEXT NOT NULL,
      text TEXT NOT NULL
    );
  `);
 
  app.decorate('db', db);
  app.addHook('onClose', async () => db.close());
}
 
export default fp(dbPlugin);

Плагин открывает файл из настройки DB_FILE, создаёт таблицы, если их ещё нет, добавляет app.db и закрывает базу при остановке сервера. ON DELETE CASCADE удалит комментарии вместе со статьёй.

Подключение

В src/app.js замените плагин store на db. Он должен идти после @fastify/env, потому что читает app.config:

// src/app.js
import dbPlugin from './plugins/db.js';
 
app.register(fastifyEnv, { schema: envSchema });
app.register(dbPlugin);
app.register(postRoutes);
app.register(commentRoutes);

После npm run dev в корне проекта появится файл blog.db. Добавьте его в .gitignore.

Первый запрос

Методы better-sqlite3 синхронные: prepare() готовит запрос, а all(), get() и run() выполняют его.

// src/routes/posts.js
app.get('/posts', async () => {
  return app.db.prepare('SELECT id, title FROM posts ORDER BY id DESC').all();
});

Откройте http://localhost:3000/posts — пока база пуста, вы увидите []. Статьи начнут появляться в следующем уроке, когда маршрут POST будет записывать их в базу.

МетодВозвращает
.all(...значения)Массив строк
.get(...значения)Одну строку или undefined
.run(...значения)changes — сколько строк изменено, lastInsertRowid — id новой строки

Частая ошибка

Нельзя вставлять данные от клиента прямо в текст запроса — это открывает дорогу SQL-инъекциям, когда злоумышленник подставляет свой SQL:

// src/routes/posts.js
// Неправильно:
app.db.prepare(`SELECT * FROM posts WHERE id = ${request.params.id}`).get();

Ставьте в запрос знак ?, а значения передавайте отдельно: prepare('SELECT * FROM posts WHERE id = ?').get(id).

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.