Encodera

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

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

Оглавление

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).

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

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