JSON-файл как хранилище
Пока у блога нет базы данных, статьи можно хранить в обычном JSON-файле. Программа читает файл, превращает текст в массив объектов, меняет массив и записывает обратно. Для учебного проекта или небольшой утилиты этого вполне достаточно.
Сохранение и чтение
Объект превращают в текст через JSON.stringify(), текст обратно в объект — через JSON.parse():
import { readFile, writeFile } from 'node:fs/promises';
const posts = [{ id: 1, title: 'Привет, Node.js', body: 'Первая статья.' }];
await writeFile('posts.json', JSON.stringify(posts, null, 2));
const saved = JSON.parse(await readFile('posts.json', 'utf8'));
console.log(saved[0].title); // Привет, Node.jsАргументы null, 2 включают отступы в два пробела — файл удобно читать и править вручную.
Если файла ещё нет
При первом запуске файла нет. В этом случае вернём пустой массив, а остальные ошибки пробросим дальше:
import { readFile } from 'node:fs/promises';
async function loadPosts(file) {
try {
return JSON.parse(await readFile(file, 'utf8'));
} catch (error) {
if (error.code === 'ENOENT') return [];
throw error;
}
}
console.log(await loadPosts('no-such-file.json')); // []Модуль хранилища
Соберите всё в модуль src/posts.js — его будет использовать сервер блога. Файл данных лежит в data/posts.json:
// src/posts.js
import path from 'node:path';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
const DATA_FILE = path.join(import.meta.dirname, '..', 'data', 'posts.json');
export async function loadPosts() {
try {
return JSON.parse(await readFile(DATA_FILE, 'utf8'));
} catch (error) {
if (error.code === 'ENOENT') return [];
throw error;
}
}
async function savePosts(posts) {
await mkdir(path.dirname(DATA_FILE), { recursive: true });
await writeFile(DATA_FILE, JSON.stringify(posts, null, 2));
}
export async function addPost(title, body) {
const posts = await loadPosts();
const id = posts.length === 0 ? 1 : Math.max(...posts.map((p) => p.id)) + 1;
const post = { id, title, body, createdAt: new Date().toISOString() };
posts.push(post);
await savePosts(posts);
return post;
}
export async function findPost(id) {
const posts = await loadPosts();
return posts.find((post) => post.id === id);
}Новой статье присваивается номер на единицу больше максимального, а дата сохраняется строкой.
Использование
Создайте в корне файл add-post.js, который добавляет статью из аргументов командной строки:
// add-post.js
import { addPost, loadPosts } from './src/posts.js';
const [title = 'Без названия', body = ''] = process.argv.slice(2);
const post = await addPost(title, body);
console.log(`Добавлена статья №${post.id}`);
console.log(`Всего статей: ${(await loadPosts()).length}`);Запустите node add-post.js "Привет, Node.js" "Первая статья" дважды — номера будут 1 и 2, а в data/posts.json появятся обе статьи.
Частая ошибка
writeFile() принимает только строки и байты: объект без JSON.stringify() вызовет ошибку ERR_INVALID_ARG_TYPE. А если превратить объект в строку как попало — через String() или шаблонную строку, — в файл попадёт [object Object], и следующий JSON.parse() упадёт:
import { readFile, writeFile } from 'node:fs/promises';
await writeFile('broken.json', String({ id: 1 }));
console.log(await readFile('broken.json', 'utf8')); // [object Object]Всегда сохраняйте JSON.stringify(data) и читайте JSON.parse(text).