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

«Можно ли унифицировать CRUD-операции в Node.js-приложении?» — вопрос из категории Архитектура, который задают на 26% собеседований 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.