Ответ
Да, разрабатывал 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;
Ключевые принципы, которые я соблюдал:
-
Использование правильных HTTP методов:
GETдля получения данныхPOSTдля созданияPUT/PATCHдля обновленияDELETEдля удаления
-
Семантические статус-коды:
// В контроллере 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' }); } }; -
Версионирование API: Всегда использовал префикс
/api/v1/для возможности обратно совместимых изменений -
Пагинация, фильтрация, сортировка:
// 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) } }); }; -
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;
А теперь по пунктам, на что я там смотрю, чтобы не облажаться:
-
HTTP-методы как по учебнику. Это же основа основ, ёпта.
GET— это получить,POST— воткнуть новое,PUT/PATCH— поправить старое,DELETE— отправить нахуй. Не надо изобретать велосипед и делать удаление через GET с параметром?action=delete. Это путь в ад. -
Статус-коды — это наше всё. Нельзя просто всегда отдавать 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, а не "ой, чёт не работает" } }; -
Версионирование. Обязательно пихаю в путь
/api/v1/. Потому что завтра придет менеджер с криком «нам срочно надо всё сломать для новой фичи!», а у тебя уже прод работает. Сделаешь/api/v2/и спишь спокойно, пока старые клиенты не накроются медным тазом сами. -
Пагинация, фильтры, сортировка. Без этого на любом более-менее живом проекте твой 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. -
Вложенные ресурсы. Тут тоже логика простая, как палка. Хочешь посты пользователя?
GET /api/v1/users/:userId/posts. Создать пост для конкретного юзера?POST /api/v1/users/:userId/posts. Не надо городить?user_id=...в основном роуте постов — это хитрая жопа, которая потом вылезет боком.
Ну и конечно, интеграция с внешним миром. Платежки, мейлеры — всё через axios, но с умом. Обязательно настраиваю retry, потому что эти сервисы любят бздеть и падать в самый неподходящий момент. А ещё circuit breaker, чтобы если внешний сервис лег, не тащить за собой в могилу и своё приложение. В общем, стандартный набор, чтобы не было потом волнения ебать в три часа ночи.