Антон Ларичев

Введение
REST API — это архитектурный стиль, который позволяет клиенту и серверу обмениваться данными по протоколу HTTP. Связка Node.js и Express остаётся одним из самых популярных способов быстро поднять backend для веб-приложения, мобильного клиента или микросервиса. В этой статье мы напишем полноценный REST-сервер с нуля: настроим маршрутизацию, добавим middleware, реализуем CRUD-операции, валидацию и грамотную обработку ошибок.
Предполагается, что у вас установлен Node.js версии 18 или выше. Проверить можно командой node -v. Дополнительно понадобится любой HTTP-клиент для тестов: Postman, Insomnia или утилита curl.
Инициализация проекта
Создадим директорию и инициализируем npm-проект. Установим Express и пару утилит, которые пригодятся в реальной разработке.
mkdir rest-api && cd rest-api
npm init -y
npm install express
npm install -D nodemon
Добавим в package.json скрипт для запуска в режиме разработки:
{
"scripts": {
"dev": "nodemon src/index.js",
"start": "node src/index.js"
}
}
Nodemon будет перезапускать сервер при каждом изменении файла, что заметно ускоряет разработку.
Минимальный сервер
Создадим файл src/index.js и опишем самый простой Express-сервер.
// Импортируем фреймворк
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware для парсинга JSON в теле запроса
app.use(express.json());
// Простой проверочный маршрут
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(PORT, () => {
console.log(`Сервер запущен на порту ${PORT}`);
});
Запустите npm run dev и откройте http://localhost:3000/health — увидите JSON с подтверждением, что сервер работает.
Структура проекта
Для учебного примера часто всё пишут в одном файле, но в реальных проектах код разбивают на слои. Минимальная разумная структура такая:
src/
index.js // точка входа
routes/
users.js // маршруты ресурса
controllers/
users.js // обработчики
middleware/
errorHandler.js // обработка ошибок
Такое разделение упрощает тестирование и помогает быстрее ориентироваться в коде, когда количество эндпоинтов растёт.
CRUD-операции для ресурса
Реализуем классический REST-ресурс — список пользователей. Для простоты данные будем хранить в памяти, чтобы не отвлекаться на работу с базой.
// src/controllers/users.js
let users = [
{ id: 1, name: 'Анна', email: 'anna@example.com' }
];
let nextId = 2;
exports.list = (req, res) => {
res.json(users);
};
exports.getOne = (req, res) => {
const id = Number(req.params.id);
const user = users.find((u) => u.id === id);
if (!user) {
return res.status(404).json({ message: 'Пользователь не найден' });
}
res.json(user);
};
exports.create = (req, res) => {
const { name, email } = req.body;
// Минимальная проверка обязательных полей
if (!name || !email) {
return res.status(400).json({ message: 'name и email обязательны' });
}
const user = { id: nextId++, name, email };
users.push(user);
res.status(201).json(user);
};
exports.update = (req, res) => {
const id = Number(req.params.id);
const user = users.find((u) => u.id === id);
if (!user) {
return res.status(404).json({ message: 'Пользователь не найден' });
}
Object.assign(user, req.body);
res.json(user);
};
exports.remove = (req, res) => {
const id = Number(req.params.id);
users = users.filter((u) => u.id !== id);
res.status(204).send();
};
Теперь подключим обработчики к маршрутам через express.Router:
// src/routes/users.js
const { Router } = require('express');
const controller = require('../controllers/users');
const router = Router();
router.get('/', controller.list);
router.get('/:id', controller.getOne);
router.post('/', controller.create);
router.put('/:id', controller.update);
router.delete('/:id', controller.remove);
module.exports = router;
И подключим роутер в точке входа:
// src/index.js
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);
Теперь доступны пять стандартных REST-эндпоинтов: GET /api/users, GET /api/users/:id, POST /api/users, PUT /api/users/:id, DELETE /api/users/:id.
Проверка через curl
curl -X POST http://localhost:3000/api/users \
-H 'Content-Type: application/json' \
-d '{"name":"Иван","email":"ivan@example.com"}'
Централизованная обработка ошибок
Вместо того чтобы повторять try/catch в каждом контроллере, удобно завести единый middleware-обработчик ошибок. Express вызывает его, когда мы передаём ошибку в next(err).
// src/middleware/errorHandler.js
module.exports = (err, req, res, next) => {
// Логируем ошибку для отладки
console.error(err);
const status = err.status || 500;
res.status(status).json({
message: err.message || 'Внутренняя ошибка сервера'
});
};
Подключаем его последним, после всех маршрутов:
const errorHandler = require('./middleware/errorHandler');
app.use(errorHandler);
Теперь в любом контроллере можно делать next(new Error('...')), и клиент получит корректный JSON-ответ с нужным статусом.
Частые ошибки
При написании первых REST-API разработчики регулярно наступают на одни и те же грабли.
- Забытый
express.json(). Без этого middlewarereq.bodyбудетundefined, и POST-запросы не будут видеть данные. - Возврат 200 вместо 201 и 204. Для создания ресурса принято использовать
201 Created, а для удаления без тела —204 No Content. - Отсутствие валидации входных данных. Минимальные проверки руками — это нормально для прототипа, но в продакшене стоит подключить библиотеку вроде
zodилиjoi. - Игнорирование CORS. Если фронтенд работает на другом домене, без пакета
corsбраузер заблокирует запросы. - Хранение секретов в коде. Пароли, токены и строки подключения должны лежать в переменных окружения и читаться через
process.env. - Отсутствие централизованной обработки ошибок. Без неё необработанное исключение может уронить весь процесс Node.js.
Заключение
Мы прошли путь от пустой папки до работающего REST API с маршрутизацией, контроллерами и обработкой ошибок. Этого достаточно, чтобы запустить прототип сервиса или внутренний инструмент. Следующие шаги — подключить базу данных (PostgreSQL, MongoDB), добавить аутентификацию через JWT, описать схему OpenAPI и покрыть код тестами на Jest или Vitest. Express даёт прочный фундамент: остальное наращивается слоями по мере роста проекта.






Комментарии
0