Можно ли унифицировать CRUD-операции в Node.js-приложении?

Ответ

Да, унификация CRUD (Create, Read, Update, Delete) — это распространенная и хорошая практика в Node.js, которая снижает дублирование кода и упрощает поддержку. Вот основные подходы:

1. Унифицированный роутинг (например, в Express): Использование router.route() позволяет группировать обработчики для одного ресурса.

const express = require('express');
const router = express.Router();

// Унифицированный маршрут для ресурса 'items'
router.route('/items')
  .get(async (req, res) => { // READ (множество)
    const items = await ItemModel.find(req.query);
    res.json(items);
  })
  .post(async (req, res) => { // CREATE
    const newItem = await ItemModel.create(req.body);
    res.status(201).json(newItem);
  });

router.route('/items/:id')
  .get(async (req, res) => { // READ (один)
    const item = await ItemModel.findById(req.params.id);
    res.json(item);
  })
  .put(async (req, res) => { // UPDATE (полное)
    const updatedItem = await ItemModel.findByIdAndUpdate(req.params.id, req.body, { new: true });
    res.json(updatedItem);
  })
  .delete(async (req, res) => { // DELETE
    await ItemModel.findByIdAndDelete(req.params.id);
    res.status(204).send();
  });

2. Базовый сервисный класс (Service/Repository Layer): Создание абстрактного класса, который инкапсулирует общую логику работы с моделью данных.

class BaseService {
  constructor(model) {
    this.model = model;
  }

  async create(data) {
    return await this.model.create(data);
  }

  async findById(id) {
    return await this.model.findById(id);
  }

  async findAll(filter = {}) {
    return await this.model.find(filter);
  }

  async update(id, data) {
    return await this.model.findByIdAndUpdate(id, data, { new: true });
  }

  async delete(id) {
    return await this.model.findByIdAndDelete(id);
  }
}

// Использование
const UserService = new BaseService(UserModel);
const newUser = await UserService.create({ name: 'John' });

3. Использование возможностей ORM/ODM (Mongoose, TypeORM): Эти библиотеки уже предоставляют унифицированные методы для CRUD.

// Mongoose предоставляет стандартизированные методы
const item = await Item.findById('someId'); // Read
const items = await Item.find({ active: true }); // Read All
const newItem = await Item.create({ name: 'New' }); // Create
await Item.findByIdAndUpdate('someId', { name: 'Updated' }); // Update
await Item.findByIdAndDelete('someId'); // Delete

Преимущества: Согласованность API, меньше повторяющегося кода, легче вводить общие улучшения (например, пагинацию, логирование). Предостережение: Не стоит чрезмерно абстрагировать уникальную бизнес-логику, которая не вписывается в шаблон CRUD.

Ответ 18+ 🔞

Э, слушай, ну это ж классика, ебать мои старые костыли! Каждый второй проект на Node.js через это проходит. Все эти CRUD-ы — Create, Read, Update, Delete — они как назойливые родственники, везде лезут. И каждый раз одно и то же писать — это же пиздец какой-то, удивление пиздец, честно. Ну не может же быть, чтобы нельзя было это как-то унифицировать, чтобы не копипастить одно и то же до овердохуища раз.

Так вот, есть несколько проверенных способов не сойти с ума.

Первый, самый простой — роутинг в Express привести в божеский вид. Вместо того чтобы раскидывать кучу отдельных маршрутов, можно собрать их в кучу. Смотри, как красивше:

const express = require('express');
const router = express.Router();

// Всё для сущности 'items' в одном месте, ёпта!
router.route('/items')
  .get(async (req, res) => { // Достаём всё (Read)
    const items = await ItemModel.find(req.query);
    res.json(items);
  })
  .post(async (req, res) => { // Создаём (Create)
    const newItem = await ItemModel.create(req.body);
    res.status(201).json(newItem);
  });

router.route('/items/:id')
  .get(async (req, res) => { // Достаём один (Read one)
    const item = await ItemModel.findById(req.params.id);
    res.json(item);
  })
  .put(async (req, res) => { // Обновляем целиком (Update)
    const updatedItem = await ItemModel.findByIdAndUpdate(req.params.id, req.body, { new: true });
    res.json(updatedItem);
  })
  .delete(async (req, res) => { // Удаляем нахуй (Delete)
    await ItemModel.findByIdAndDelete(req.params.id);
    res.status(204).send();
  });

Видишь? Всё аккуратно, по полочкам. Не размазано по десяти файлам. Читать приятно, понимаешь, где что искать.

Второй подход — сделать какого-нибудь универсального солдата, сервисный класс. Это когда ты выносишь всю рутину в отдельное место, а в контроллерах только вызываешь. Типа так:

class BaseService {
  constructor(model) {
    this.model = model; // Подсовываем любую модель — и работает!
  }

  async create(data) {
    return await this.model.create(data);
  }

  async findById(id) {
    return await this.model.findById(id);
  }

  async findAll(filter = {}) {
    return await this.model.find(filter);
  }

  async update(id, data) {
    return await this.model.findByIdAndUpdate(id, data, { new: true });
  }

  async delete(id) {
    return await this.model.findByIdAndDelete(id);
  }
}

// Используем — просто праздник какой-то!
const UserService = new BaseService(UserModel);
const newUser = await UserService.create({ name: 'Васян' });

Получается, что для каждой новой сущности ты не пишешь сервис с нуля, а просто создаёшь инстанс этого базового класса. Экономия времени — просто ядрёна вошь!

Ну и третий вариант — просто пользоваться тем, что дают библиотеки. Возьмём Mongoose — он же из коробки всё умеет. Зачем изобретать велосипед?

// Всё уже есть, ёпта! Бери и пользуйся.
const item = await Item.findById('someId'); // Нашли один
const items = await Item.find({ active: true }); // Нашли все активные
const newItem = await Item.create({ name: 'Новый' }); // Создали
await Item.findByIdAndUpdate('someId', { name: 'Обновлённый' }); // Поправили
await Item.findByIdAndDelete('someId'); // Отправили в утиль

В чём профит, спросишь? Да всё просто: код становится предсказуемым. Зашёл в любой контроллер — там одинаковая структура. Хочешь добавить пагинацию или логирование — поправил в одном месте, и оно везде применилось. Не надо бегать по всем файлам, как угорелый.

Но! Есть одно важное «но», чувак. Не надо сходить с ума и пытаться впихнуть в эту унификацию всю свою бизнес-логику. Если у тебя есть какая-то хитрая жопа операция, которая в стандартный CRUD ну никак не лезет — вынеси её отдельно. Не делай из базового сервиса монстра с сотней методов, а то потом сам от себя охуеешь, когда его поддерживать придётся. Всё должно быть в меру.