CRUD-маршруты на практике
CRUD: Create, Read, Update, Delete — четыре операции, покрывающие почти любой ресурс.
«Освоив CRUD для одного ресурса, ты умеешь делать API для любого: меняется только название.»
CRUD — это четыре базовые операции над данными. Реализовав их для одного ресурса, ты получаешь шаблон для всех остальных. В этом уроке напишем полный набор маршрутов для ресурса "задачи" с хранением в памяти — без базы, чтобы сфокусироваться на логике API.
Полный CRUD
Соберём все шесть маршрутов вокруг массива в памяти. В реальном приложении массив заменит база, но интерфейс останется тем же:
let tasks = [{ id: 1, title: 'Выучить Express', done: false }];
let nextId = 2;
// READ: список
app.get('/tasks', (req, res) => res.json(tasks));
// READ: один
app.get('/tasks/:id', (req, res) => {
const task = tasks.find(t => t.id === Number(req.params.id));
if (!task) return res.status(404).json({ error: 'не найдено' });
res.json(task);
});
// CREATE
app.post('/tasks', (req, res) => {
const task = { id: nextId++, title: req.body.title, done: false };
tasks.push(task);
res.status(201).json(task);
});
// UPDATE (частично)
app.patch('/tasks/:id', (req, res) => {
const task = tasks.find(t => t.id === Number(req.params.id));
if (!task) return res.status(404).json({ error: 'не найдено' });
Object.assign(task, req.body);
res.json(task);
});
// DELETE
app.delete('/tasks/:id', (req, res) => {
tasks = tasks.filter(t => t.id !== Number(req.params.id));
res.status(204).end();
});Обрати внимание на повторяющийся паттерн: найти ресурс, проверить наличие (404 если нет), выполнить действие, вернуть результат с правильным статусом.
CRUD над массивом в браузере
Чтобы прочувствовать логику без сервера, реализуем CRUD над обычным массивом. Это та же бизнес-логика, что внутри обработчиков выше:
let items = [];
let id = 1;
function create(title) {
const item = { id: id++, title };
items.push(item);
return item;
}
function read(itemId) { return items.find(i => i.id === itemId) || null; }
function update(itemId, title) {
const item = read(itemId);
if (item) item.title = title;
return item;
}
function remove(itemId) { items = items.filter(i => i.id !== itemId); }
create('Первая');
create('Вторая');
update(1, 'Обновлённая');
remove(2);
console.log(items); // [{ id: 1, title: 'Обновлённая' }]Жизненный цикл записи
POST -> создаётся -> [ запись существует ] GET -> читается -> [ запись существует ] PATCH -> меняется -> [ запись изменена ] DELETE -> удаляется -> [ записи больше нет -> 404 на GET ]
Как работает под капотом
Каждый CRUD-маршрут — это обычный обработчик Express, который читает req.params/req.body, меняет источник данных и отвечает через res.json()/res.status(). Разница между "в памяти" и "в базе" — только в том, чем заменить массив. Поэтому, отладив логику на массиве, ты легко перенесёшь её на реальное хранилище.
Частые ошибки
- Не проверять наличие. Перед update/delete убедись, что ресурс есть, иначе клиент не отличит успех от "ничего не нашлось".
- Сравнивать id строкой.
req.params.id— строка, элементы массива хранят число. Преобразуй черезNumber(). - Возвращать 200 на удаление без тела. Чище отдать 204 No Content.
Best practices
- Выноси поиск ресурса в отдельную функцию или middleware, чтобы не дублировать.
- Отвечай согласованными статусами: 201, 200, 204, 404.
- Не возвращай внутренние поля (пароли, токены) — формируй ответ явно.
Итоги
CRUD — это четыре операции и предсказуемый набор статусов. Логика всех ресурсов однотипна, поэтому её удобно шаблонизировать. Сейчас все маршруты лежат в одном файле; в следующем уроке наведём порядок с помощью express.Router.
Пагинация и форма коллекции
Список ресурсов почти никогда не отдают целиком: представь таблицу в миллион строк. Поэтому коллекции отдают порциями, управляя ими через query: ?page=2&limit=20 или через курсор. Ответ при этом удобно завернуть в объект с метаданными — сколько всего записей, какая страница, есть ли следующая — а не отдавать голый массив. Это даёт клиенту контекст и оставляет место для расширения, не ломая контракт. Привыкай проектировать списки с пагинацией с самого начала: добавить её в работающее API задним числом почти всегда означает ломающее изменение для всех клиентов сразу.