Использовали ли вы REST API в Node.js проектах?

Ответ

Да, разрабатывал RESTful API на Node.js для множества проектов, используя Express.js и следуя общепринятым best practices.

Пример структуры REST API, которую я реализовывал:

// routes/users.js
const express = require('express');
const router = express.Router();
const UserController = require('../controllers/userController');
const { validateUser } = require('../middleware/validation');

// GET /api/v1/users - Получить список пользователей с пагинацией
router.get('/', UserController.getAllUsers);

// GET /api/v1/users/:id - Получить конкретного пользователя
router.get('/:id', UserController.getUserById);

// POST /api/v1/users - Создать нового пользователя
router.post('/', validateUser, UserController.createUser);

// PUT /api/v1/users/:id - Полное обновление пользователя
router.put('/:id', validateUser, UserController.updateUser);

// PATCH /api/v1/users/:id - Частичное обновление
router.patch('/:id', UserController.partialUpdateUser);

// DELETE /api/v1/users/:id - Удалить пользователя
router.delete('/:id', UserController.deleteUser);

module.exports = router;

Ключевые принципы, которые я соблюдал:

  1. Использование правильных HTTP методов:

    • GET для получения данных
    • POST для создания
    • PUT/PATCH для обновления
    • DELETE для удаления
  2. Семантические статус-коды:

    // В контроллере
    const getUserById = async (req, res) => {
    try {
    const user = await User.findById(req.params.id);
    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }
    res.status(200).json(user);
    } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
    }
    };
  3. Версионирование API: Всегда использовал префикс /api/v1/ для возможности обратно совместимых изменений

  4. Пагинация, фильтрация, сортировка:

    // GET /api/v1/users?page=1&limit=20&sort=-createdAt&status=active
    const getAllUsers = async (req, res) => {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 20;
    const skip = (page - 1) * limit;
    
    const filter = {};
    if (req.query.status) filter.status = req.query.status;
    
    const sort = {};
    if (req.query.sort) {
    const sortField = req.query.sort.startsWith('-') ? req.query.sort.substring(1) : req.query.sort;
    sort[sortField] = req.query.sort.startsWith('-') ? -1 : 1;
    }
    
    const users = await User.find(filter)
    .sort(sort)
    .skip(skip)
    .limit(limit);
    
    const total = await User.countDocuments(filter);
    
    res.json({
    data: users,
    pagination: { page, limit, total, pages: Math.ceil(total / limit) }
    });
    };
  5. REST для вложенных ресурсов:

    • GET /api/v1/users/:userId/posts - Получить посты пользователя
    • POST /api/v1/users/:userId/posts - Создать пост для пользователя

Также интегрировал REST API с внешними сервисами (платежные системы, email-сервисы) используя axios с retry logic и circuit breaker.

Ответ 18+ 🔞

А, ну это ж классика, ебать мои старые костыли! REST API на Node.js — это как хлеб с маслом, только масло иногда прогорклое, а хлеб черствый, но в целом жить можно.

Смотри, вот как я обычно это делаю, чтобы не было потом стыдно перед самим собой и чтобы код не выглядел как пиздопроебибна какая-то.

Вот пример, как я обычно структурирую роуты, чтобы потом не охуеть от бардака:

// routes/users.js
const express = require('express');
const router = express.Router();
const UserController = require('../controllers/userController');
const { validateUser } = require('../middleware/validation');

// GET /api/v1/users - Получить список пользователей с пагинацией
router.get('/', UserController.getAllUsers);

// GET /api/v1/users/:id - Получить конкретного пользователя
router.get('/:id', UserController.getUserById);

// POST /api/v1/users - Создать нового пользователя
router.post('/', validateUser, UserController.createUser);

// PUT /api/v1/users/:id - Полное обновление пользователя
router.put('/:id', validateUser, UserController.updateUser);

// PATCH /api/v1/users/:id - Частичное обновление
router.patch('/:id', UserController.partialUpdateUser);

// DELETE /api/v1/users/:id - Удалить пользователя
router.delete('/:id', UserController.deleteUser);

module.exports = router;

А теперь по пунктам, на что я там смотрю, чтобы не облажаться:

  1. HTTP-методы как по учебнику. Это же основа основ, ёпта. GET — это получить, POST — воткнуть новое, PUT/PATCH — поправить старое, DELETE — отправить нахуй. Не надо изобретать велосипед и делать удаление через GET с параметром ?action=delete. Это путь в ад.

  2. Статус-коды — это наше всё. Нельзя просто всегда отдавать 200, даже если всё сломалось. Это же доверия ебать ноль у клиента будет.

    // В контроллере
    const getUserById = async (req, res) => {
      try {
        const user = await User.findById(req.params.id);
        if (!user) {
          return res.status(404).json({ error: 'User not found' }); // Не нашли — 404, а не 200 с null
        }
        res.status(200).json(user); // Всё ок — 200
      } catch (error) {
        res.status(500).json({ error: 'Internal server error' }); // Сервер упал — 500, а не "ой, чёт не работает"
      }
    };
  3. Версионирование. Обязательно пихаю в путь /api/v1/. Потому что завтра придет менеджер с криком «нам срочно надо всё сломать для новой фичи!», а у тебя уже прод работает. Сделаешь /api/v2/ и спишь спокойно, пока старые клиенты не накроются медным тазом сами.

  4. Пагинация, фильтры, сортировка. Без этого на любом более-менее живом проекте твой API накроется медным тазом от первой же выборки на 10 миллионов записей.

    // GET /api/v1/users?page=1&limit=20&sort=-createdAt&status=active
    const getAllUsers = async (req, res) => {
      const page = parseInt(req.query.page) || 1;
      const limit = parseInt(req.query.limit) || 20;
      const skip = (page - 1) * limit;
    
      const filter = {};
      if (req.query.status) filter.status = req.query.status;
    
      const sort = {};
      if (req.query.sort) {
        const sortField = req.query.sort.startsWith('-') ? req.query.sort.substring(1) : req.query.sort;
        sort[sortField] = req.query.sort.startsWith('-') ? -1 : 1;
      }
    
      const users = await User.find(filter)
        .sort(sort)
        .skip(skip)
        .limit(limit);
    
      const total = await User.countDocuments(filter);
    
      res.json({
        data: users,
        pagination: { page, limit, total, pages: Math.ceil(total / limit) }
      });
    };

    Главное — документировать эти параметры, а то фронтендеры потом придут и спросят «какого хуя не работает?», а у них в запросе ?sortBy=desc.

  5. Вложенные ресурсы. Тут тоже логика простая, как палка. Хочешь посты пользователя? GET /api/v1/users/:userId/posts. Создать пост для конкретного юзера? POST /api/v1/users/:userId/posts. Не надо городить ?user_id=... в основном роуте постов — это хитрая жопа, которая потом вылезет боком.

Ну и конечно, интеграция с внешним миром. Платежки, мейлеры — всё через axios, но с умом. Обязательно настраиваю retry, потому что эти сервисы любят бздеть и падать в самый неподходящий момент. А ещё circuit breaker, чтобы если внешний сервис лег, не тащить за собой в могилу и своё приложение. В общем, стандартный набор, чтобы не было потом волнения ебать в три часа ночи.