Encodera

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

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

Оглавление

База данных SQLite

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

Установка

npm install better-sqlite3

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

Подключение и таблицы

Создайте файл src/db.js. Он открывает базу и создаёт таблицы, если их ещё нет:

// src/db.js
import Database from 'better-sqlite3';
 
const db = new Database('blog.db');
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
  );
`);
 
export default db;

При первом запуске в корне проекта появится файл blog.db. Поле post_id связывает комментарий со статьёй, а ON DELETE CASCADE удалит комментарии вместе со статьёй.

Модель

Все запросы к таблицам собирают в модель. Создайте src/models/post.js:

// src/models/post.js
import db from '../db.js';
 
export function findAll() {
  return db.prepare('SELECT * FROM posts ORDER BY id DESC').all();
}
 
export function findById(id) {
  return db.prepare('SELECT * FROM posts WHERE id = ?').get(id);
}
 
export function create({ title, body }) {
  const result = db.prepare('INSERT INTO posts (title, body) VALUES (?, ?)').run(title, body);
  return Number(result.lastInsertRowid);
}
 
export function findComments(postId) {
  return db.prepare('SELECT * FROM comments WHERE post_id = ? ORDER BY id').all(postId);
}
 
export function addComment(postId, { author, text }) {
  db.prepare('INSERT INTO comments (post_id, author, text) VALUES (?, ?, ?)').run(postId, author, text);
}

prepare() готовит запрос, а методы выполняют его: all() возвращает массив строк, get() — одну строку или undefined, run() — сведения об изменении. Знаки ? заменяются аргументами по порядку.

Использование в контроллере

Контроллер теперь берёт данные из модели вместо массива:

// src/controllers/postsController.js
import * as Post from '../models/post.js';
 
export function index(req, res) {
  res.render('posts/index', { title: 'Все статьи', posts: Post.findAll() });
}
 
export function show(req, res) {
  const post = Post.findById(Number(req.params.id));
 
  if (!post) {
    return res.status(404).render('404', { title: 'Не найдено' });
  }
 
  res.render('posts/show', { title: post.title, post, comments: Post.findComments(post.id) });
}
 
export function storeComment(req, res) {
  Post.addComment(Number(req.params.id), req.body);
  res.redirect(`/posts/${req.params.id}`);
}

Маршрут для комментариев: router.post('/:id/comments', postsController.storeComment), а форма на странице статьи отправляет поля author и text на /posts/<%= post.id %>/comments. Создайте пару статей и перезапустите сервер — они останутся на месте.

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

Никогда не вставляйте данные посетителя в SQL через шаблонную строку:

// src/models/post.js
// Неправильно: значение из адреса попадает прямо в SQL.
db.prepare(`SELECT * FROM posts WHERE id = ${id}`).get();

Вместо числа в адрес можно подставить кусок SQL и прочитать или удалить чужие данные — это SQL-инъекция. Всегда используйте знаки ? и передавайте значения аргументами: better-sqlite3 подставит их безопасно.

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

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