Можно ли использовать JSON-файл как базу данных? Можно, но только для небольших данных, которые редко меняются и которые пишет один процесс. Как только появляется конкурентная запись, частые обновления или данные начинают расти, JSON-файл становится источником потерь и повреждений. Ниже — где граница и как её не пропустить.
Что такое JSON как база данных
Это паттерн, при котором вместо СУБД вы читаете и пишете структурированные данные в текстовый файл .json. Никаких серверов, подключений и лицензий — просто файл на диске.
{
"users": [
{ "id": 1, "name": "Иван", "email": "ivan@example.com" },
{ "id": 2, "name": "Мария", "email": "maria@example.com" }
],
"settings": {
"theme": "dark",
"language": "ru"
}
}
Подход встречается повсюду: конфигурации (package.json, tsconfig.json), простые CLI-инструменты, прототипы, фронтенд-моки. Проблемы начинаются, когда стирается граница между «хранить конфиг» и «хранить бизнес-данные».
Преимущества JSON-файла
1. Нулевая настройка
Не нужно ставить PostgreSQL, заводить пользователей и пробрасывать порты в Docker. Удобно для прототипов, хакатонов и MVP за выходные.
2. Человекочитаемость
Открыли в редакторе — увидели все данные, без SQL-клиентов.
3. Поддержка во всех языках
Python, JavaScript, Go, PHP, Ruby — парсинг JSON есть в стандартной библиотеке или в одном пакете.
4. Простой перенос
Скопировали файл — передали коллеге или перенесли на другую машину.
5. Гибкая схема
Новое поле в одной записи ничего не ломает. Удобно, пока структура данных не устоялась.
Недостатки: почему JSON-файл — не настоящая база данных
Нет транзакций
Если процесс упадёт посреди записи, а файл перезаписывается «на месте», он останется обрезанным и не распарсится. В SQLite и PostgreSQL незавершённая транзакция откатывается автоматически. С JSON защититься можно только атомарной записью: писать во временный файл и переименовывать его поверх старого.
import json, os, tempfile
def save_json(path: str, data) -> None:
directory = os.path.dirname(os.path.abspath(path))
fd, tmp = tempfile.mkstemp(dir=directory, suffix='.tmp')
with os.fdopen(fd, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
f.flush()
os.fsync(f.fileno())
os.replace(tmp, path) # атомарная замена в пределах одной файловой системы
Атомарная запись защищает от обрезанного файла, но не от потерянных обновлений — о них ниже.
Нет параллельного доступа
Два процесса или два запроса одновременно читают файл, меняют каждый своё и записывают — побеждает последний, изменения первого теряются. В веб-приложении с несколькими одновременными пользователями это вопрос времени. Нужны блокировки файла или единственный процесс-писатель, то есть вы начинаете писать свою СУБД.
Производительность падает с ростом данных
Чтобы обновить одну запись, нужно:
- прочитать весь файл в память;
- найти объект;
- изменить его;
- перезаписать весь файл.
Стоимость каждой записи растёт вместе с размером файла. Пока файл маленький, это незаметно, но при частых обновлениях и росте данных задержки становятся ощутимыми.
Нет индексов и запросов
В SQL условие WHERE user_id = 42 использует индекс. В JSON-файле приходится перебирать все записи (filter, find) — O(n) на каждый запрос.
Нет разграничения прав
В PostgreSQL можно дать доступ только на чтение отдельных таблиц. JSON-файл доступен целиком или не доступен вовсе.
Бинарные данные
Изображения и файлы приходится кодировать в Base64, что увеличивает их размер примерно на треть и утяжеляет каждую запись файла.
Библиотеки для JSON-хранилища
Если всё же используете JSON-файл, берите библиотеку, а не ручные JSON.parse/JSON.stringify — но проверяйте, что именно она гарантирует.
lowdb (Node.js / TypeScript)
Популярная библиотека для JSON-хранилища в Node.js. Поддерживает TypeScript, адаптеры для файла и памяти; файловый адаптер пишет файл атомарно.
npm install lowdb
import { Low } from 'lowdb'
import { JSONFile } from 'lowdb/node'
type Data = { users: { id: number; name: string }[] }
const adapter = new JSONFile<Data>('db.json')
const db = new Low(adapter, { users: [] })
await db.read()
db.data.users.push({ id: 1, name: 'Иван' })
await db.write()
Подходит для Electron-приложений, CLI-инструментов и небольших локальных утилит. От конкурентной записи из нескольких процессов lowdb не защищает.
TinyDB (Python)
Небольшая документная база для Python с синтаксисом запросов, напоминающим MongoDB. Проект считается завершённым по функциональности и в основном поддерживается, а не развивается.
pip install tinydb
from tinydb import TinyDB, Query
db = TinyDB('db.json')
User = Query()
db.insert({'name': 'Иван', 'role': 'admin'})
result = db.search(User.role == 'admin')
print(result)
Подходит для небольших скриптов и прототипов. Стандартное хранилище TinyDB перезаписывает файл на месте, поэтому параллельная запись из нескольких процессов небезопасна, а для критичных данных стоит подумать о SQLite.
JSON Server (Node.js)
Не база данных, а инструмент для mock REST API поверх JSON-файла: фронтенд можно разрабатывать без реального бэкенда.
npx json-server db.json --port 3001
После запуска доступны эндпоинты вида GET /users, POST /users, PUT /users/1, DELETE /users/1. В старых версиях (0.x) для отслеживания изменений файла нужен флаг --watch.
Подходит для моков API, демо и интеграционных тестов фронтенда.
JSON vs SQLite
Главная альтернатива JSON-файлу — SQLite: тоже без сервера и в одном файле, но с гарантиями настоящей СУБД. Подробнее — в статье что такое SQLite.
| Критерий | JSON-файл | SQLite |
|---|---|---|
| Установка | Ничего не нужно | Python — стандартная библиотека; Node.js — better-sqlite3 или встроенный node:sqlite; Go — драйвер |
| Транзакции | Нет | ACID |
| Индексы | Нет | Есть |
| Параллельный доступ | Опасен | Много читателей и один писатель (WAL) |
| Поиск | O(n) перебор | O(log n) по индексу |
| Комфортный объём | Небольшие файлы | От килобайт до очень больших баз |
| Человекочитаемость | Да | Нет (бинарный формат) |
| Бэкап | Копия файла после атомарной записи | .backup или VACUUM INTO |
| Продакшен | Конфиги и небольшие данные | Да: мобильные приложения, браузеры, серверы |
SQLite хранит историю и закладки Firefox, данные множества мобильных приложений и системных сервисов iOS и Android. Это не учебная игрушка, а проверенное решение.
JSON внутри реляционной базы
Современные СУБД хранят JSON как тип данных в обычной таблице. Это снимает главные недостатки JSON-файла и сохраняет гибкость.
PostgreSQL JSONB
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
attributes JSONB
);
INSERT INTO products (name, attributes)
VALUES ('Ноутбук', '{"brand": "Apple", "ram": 16, "tags": ["M3", "MacBook"]}');
-- GIN-индекс ускоряет операторы вхождения вроде @>
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
SELECT * FROM products WHERE attributes @> '{"brand": "Apple"}';
-- Для частых запросов по одному полю удобнее индекс по выражению
CREATE INDEX idx_products_brand ON products ((attributes->>'brand'));
SELECT * FROM products WHERE attributes->>'brand' = 'Apple';
JSONB — бинарное представление JSON в PostgreSQL: данные индексируются, запросы быстрые, а рядом остаются транзакции и строгие колонки.
MySQL / MariaDB JSON
MySQL с версии 5.7 поддерживает тип JSON с функциями JSON_EXTRACT, JSON_SET и проверкой, что значение — корректный JSON. Индексировать поля внутри JSON можно через генерируемые колонки.
Когда JSON-файл — правильный выбор
- Конфигурация приложения (настройки, переводы, темы).
- Seed-данные для тестов.
- Mock API при фронтенд-разработке.
- Локальный кэш настроек в CLI-инструменте.
- Небольшие пользовательские данные в десктопном приложении с одним процессом-писателем.
- Прототипы, которые точно не пойдут в продакшен.
Когда JSON-файл — антипаттерн
- Несколько одновременных пользователей или процессов-писателей.
- Тысячи записей и больше.
- Нужны фильтрация, сортировка, агрегация.
- Данные часто обновляются.
- Это продакшен с бизнес-данными.
- Нужна история изменений или откат.
Бэкап JSON-файла
JSON-файл уязвим, как любой другой: случайное удаление, перезапись сломанными данными, сбой диска. Минимум — копия с датой в имени и хранение нескольких последних версий:
mkdir -p backups
cp db.json "backups/db_$(date +%Y%m%d_%H%M%S).json"
ls -t backups/db_*.json | tail -n +15 | xargs -r rm -f # оставить 14 последних
Копируйте файл в момент, когда в него никто не пишет, или после атомарной записи — иначе в копию может попасть обрезанный файл. Перед сохранением полезно проверить, что JSON парсится: python -m json.tool db.json > /dev/null.
Если хочется хранить версии вне машины, JSON-файл можно загружать в dbsend как произвольный файл (-e generic_file): dbsend будет хранить версии и проверять контрольную сумму, но не разбирать содержимое. Для SQLite dbsend показывает таблицы и число строк, а на платных тарифах — diff между версиями; подробнее на странице бэкап SQLite в dbsend.
Миграция с JSON на SQLite
- Возьмите встроенный модуль
sqlite3(Python) илиbetter-sqlite3(Node.js). - Сделайте копию JSON-файла.
- Создайте в SQLite таблицы под вашу структуру.
- Вставьте записи в одной транзакции — так быстрее и атомарно.
- Сравните количество записей до и после.
- Замените файловые операции в коде на SQL-запросы.
import json
import sqlite3
with open('db.json', encoding='utf-8') as f:
data = json.load(f)
conn = sqlite3.connect('app.db')
conn.execute('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)')
with conn: # одна транзакция: либо всё, либо ничего
conn.executemany(
'INSERT INTO users (id, name, email) VALUES (?, ?, ?)',
[(u['id'], u['name'], u.get('email')) for u in data['users']],
)
count = conn.execute('SELECT COUNT(*) FROM users').fetchone()[0]
print(f"В JSON: {len(data['users'])}, в SQLite: {count}")
conn.close()
После переезда не забудьте про бэкапы самой SQLite-базы — как делать их без повреждения, описано в статье как сделать бэкап SQLite.
Итог
Данные меняются редко и их мало — JSON. Данные меняются часто, их много или пишут несколько процессов — минимум SQLite, а для серверного приложения — PostgreSQL с JSONB для гибких полей.
Частые вопросы
Можно ли использовать JSON-файл в продакшене?
Для конфигурации и небольших редко меняющихся данных — да. Для бизнес-данных приложения — нет: берите SQLite или PostgreSQL.
Чем JSON-файл отличается от SQLite?
SQLite — полноценная реляционная СУБД в одном файле: транзакции, индексы, конкурентное чтение и SQL. JSON — текстовый формат без гарантий целостности; всё, что выше простого чтения и записи, придётся писать самому.
Какие библиотеки работают с JSON как с БД?
Для Node.js — lowdb, для Python — TinyDB или встроенный модуль json с атомарной записью, для моков API — JSON Server. Ни одна из них не решает проблему конкурентной записи из нескольких процессов.
JSONB в PostgreSQL — это то же самое, что JSON-файл?
Нет. JSONB — тип данных внутри PostgreSQL: хранится в бинарном виде, индексируется, поддерживает операторы поиска и участвует в транзакциях.
Что выбрать для прототипа — JSON или SQLite?
Если прототип точно не пойдёт в продакшен — JSON подойдёт. Если есть шанс, что проект вырастет, сразу берите SQLite: в Python модуль встроен, в Node.js есть better-sqlite3 и встроенный модуль node:sqlite в новых версиях (пока экспериментальный).
Как защитить JSON-файл от повреждения?
Пишите атомарно (временный файл + переименование), держите один процесс-писатель и делайте регулярные копии. Если нужны параллельная запись и транзакции — это сигнал переходить на SQLite.



