Гид по технологиям

Простой REST API на Node.js без фреймворка

6 min read Backend Обновлено 24 Nov 2025
Простой REST API на Node.js без фреймворка
Простой REST API на Node.js без фреймворка

человек держит стикер Node.js

Введение

Node.js — это открытая среда выполнения JavaScript на движке V8 от Google. Она позволяет запускать JavaScript вне браузера и хорошо подходит для серверных приложений благодаря событийной модели, большому экосистему пакетов и высокой производительности.

В этой инструкции мы построим простой CRUD API для блога на чистом Node.js (модуль http) и MongoDB с использованием mongoose. Это учебный пример: он показывает базовую структуру и логику, которые обычно подменяют фреймворки (Express, Fastify и т.д.).

Важно: пример предназначен для локальной разработки и обучения. Перед развёртыванием в продакшен примените дополнительные меры безопасности и наблюдаемости.

Что вы получите

  • Минимальный сервер на http.
  • Подключение к MongoDB через mongoose.
  • Модель Blog с двумя полями: title и body.
  • Роуты для GET/POST/PUT/DELETE.
  • Чек-листы для разработчика, оператора и ревьюера.
  • Советы по безопасности, тестированию и миграции на фреймворк.

Подготовка среды разработки

  1. Создайте директорию проекта и перейдите в неё:
mkdir nodejs-api
cd nodejs-api
  1. Инициализируйте npm:
npm init -y
  1. Установите mongoose (ODM для MongoDB):
npm install mongoose
  1. Создайте файл server.js в корне проекта. Пример начальной конфигурации сервера:
const http = require("http");
const server = http.createServer((req, res) => {});

server.listen(3000, () => {
  console.log("Server is running");
});

Пояснение: модуль http встроен в Node.js. Метод createServer создаёт сервер и принимает callback с объектами req и res. Метод listen запускает прослушивание порта (в примере 3000).

  1. Создайте папки routes и models в корне проекта. В routes будет логика маршрутизации, в models — схема/модель для БД.

Подключение к базе данных

В server.js импортируйте mongoose и подключитесь к MongoDB:

const mongoose = require("mongoose");

mongoose.connect("MongoDB_URI")
  .then(() => console.log('MongoDB connected'))
  .catch(err => console.error('MongoDB connection error', err));

Замените “MongoDB_URI” на строку подключения (например, mongodb://localhost:27017/mydb или URI из облачного провайдера). Не храните учётные данные в коде — используйте переменные окружения.

Создание модели данных

В models создайте файл blogModel.js со схемой и экспортом модели:

const mongoose = require("mongoose");

const blogSchema = mongoose.Schema({
  title: {
    type: String,
    required: [true, "Blog must have a title"],
  },

  body: {
    type: String,
    required: [true, "Blog must have a body"],
  },
});

module.exports = mongoose.model("Blog", blogSchema);

Кратко: схема объявляет поля title и body как строки и делает их обязательными. Экспорт модели позволяет взаимодействовать с коллекцией через методы mongoose (find, findById, save, findByIdAndUpdate, findByIdAndDelete).

Реализация маршрутов (без фреймворка)

Без Express придётся вручную парсить URL, метод и тело запроса. Создадим routes/blogRoutes.js и экспортируем асинхронную функцию router(req, res).

const Blog = require("../models/blogModel");

const router = async function (req, res) {};

module.exports = router;

Ниже — полная и понятная реализация обработчиков для основных REST-операций. Вставьте этот код внутрь router-функции в том же порядке.

GET: получить все блоги

// GET: /api/blogs
if (req.url === "/api/blogs" && req.method === "GET") {
  // get all blogs
  const blogs = await Blog.find();

  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify(blogs));
}

Пояснение: проверяем точный URL и метод. find() возвращает все документы.

GET: получить один блог по id

// GET: /api/blogs/:id
if (req.url.match(/\/api\/blogs\/([0-9]+)/) && req.method === "GET") {
  try {
    const id = req.url.split("/")[3];

    const blog = await Blog.findById(id);

    if (blog) {
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify(blog));
    } else {
      throw new Error("Blog does not exist");
    }
  } catch (error) {
    res.writeHead(404, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ message: error.message }));
  }
}

Заметка: пример использует регулярное выражение для предварительной проверки формата URL и split для извлечения id. В продакшен-решении стоит использовать более надёжную валидацию id (ObjectId для MongoDB).

POST: создать новый блог

// POST: /api/blogs
if (req.url === "/api/blogs" && req.method === "POST") {
  try {
    let body = "";

    req.on("data", (chunk) => {
      body += chunk.toString();
    });

    req.on("end", async () => {
      let blog = new Blog(JSON.parse(body));

      await blog.save();
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify(blog));
    });
  } catch (error) {
    console.log(error);
  }
}

Пояснение: req — поток (ReadableStream). Мы собираем данные в строку, затем парсим JSON и создаём новый документ.

PUT: обновить блог по id

// PUT: /api/blogs/:id
if (req.url.match(/\/api\/blogs\/([0-9]+)/) && req.method === "PUT") {
  try {
    const id = req.url.split("/")[3];
    let body = "";

    req.on("data", (chunk) => {
      body += chunk.toString();
    });

    req.on("end", async () => {
      let updatedBlog = await Blog.findByIdAndUpdate(id, JSON.parse(body), {
        new: true,
      });

      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify(updatedBlog));
    });
  } catch (error) {
    console.log(error);
  }
}

PUT похож на POST, но извлекает id и вызывает findByIdAndUpdate.

DELETE: удалить блог по id

// DELETE: /api/blogs/:id
if (req.url.match(/\/api\/blogs\/([0-9]+)/) && req.method === "DELETE") {
  try {
    const id = req.url.split("/")[3];

    await Blog.findByIdAndDelete(id);
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ message: "Blog deleted successfully" }));
  } catch (error) {
    res.writeHead(404, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ message: error.message }));
  }
}

Подключение маршрутизатора в server.js

Импортируйте router и передайте req, res при создании сервера:

const router = require("./routes/blogRoutes");

const server = http.createServer((req, res) => {
  router(req, res);
});

Теперь сервер сможет обрабатывать запросы, делегируя логику в router.

Практические замечания и когда этот подход не подходит

Важно: ручная маршрутизация без фреймворка — отличный способ понять HTTP и Node.js, но у неё есть ограничения:

  • Когда нагрузка и число роутов растут, код усложняется.
  • Отсутствуют удобные middleware (логирование, парсинг, обработка ошибок, CORS).
  • Трудно поддерживать и тестировать без стандартных абстракций.

Используйте чистый Node.js для учебных целей, прототипов и очень лёгких микросервисов. Для реального продукта чаще выбирают Express, Fastify или NestJS.

Альтернативные подходы

  • Express: минимальный, понятный API, множество middleware.
  • Fastify: высокопроизводительный фреймворк с валидацией схем и плагинами.
  • NestJS: структура для крупных приложений, использует декораторы и DI.

Перемиграция на фреймворк обычно требует замены ручной маршрутизации на роутеры и middleware. Для миграции подготовьте тесты и карты соответствия URL → контроллер.

Мини-методология разработки

  1. Сделать минимально рабочий сервер и модель.
  2. Добавить ручные тесты с curl/HTTPie.
  3. Написать автоматические тесты для каждого эндпоинта.
  4. Настроить логирование и обработку ошибок.
  5. Провести ревью безопасности перед развёртыванием.

Модель мышления и эвристики

  • Разделяй ответственность: routes — маршрутизация, models — схема, server — инициализация и подключение.
  • Обрабатывай все ветки ошибок: падения парсинга, ошибки БД, невалидные id.
  • Не доверяй входным данным: валидируй и нормализуй.

Безопасность и жёсткое поведение в продакшене

  • Не записывайте URI БД в код: используйте переменные окружения.
  • Ограничьте тело запроса по размеру (защита от DoS).
  • Валидация и экранирование полей (в данном примере — простые строки).
  • Настройка CORS, если API доступно из браузера.
  • Логи и метрики: регистрируйте ошибки и время ответа.
  • Ограничение количества попыток запросов (rate limiting) на уровне прокси/веб-сервера.

Пример ограничения размера тела (вручную):

let body = "";
const MAX_SIZE = 1e6; // ~1MB
req.on('data', chunk => {
  body += chunk.toString();
  if (body.length > MAX_SIZE) {
    res.writeHead(413, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'Payload too large' }));
    req.connection.destroy();
  }
});

Тестирование и критерии приёмки

Критерии приёмки для базового CRUD API:

  • GET /api/blogs возвращает список блогов и код 200.
  • GET /api/blogs/:id для существующего id возвращает код 200 и объект.
  • GET /api/blogs/:id для несуществующего id возвращает код 404.
  • POST /api/blogs с валидным телом создаёт запись и возвращает код 200.
  • PUT /api/blogs/:id изменяет данные и возвращает обновлённый объект.
  • DELETE /api/blogs/:id удаляет запись и возвращает подтверждение.

Тесты можно реализовать с помощью mocha/chai или Jest и supertest (для фреймворков) либо с помощью прямых HTTP-запросов (node-fetch, axios).

Чек-листы по ролям

Разработчик:

  • Написать и запустить unit-тесты и интеграционные тесты.
  • Добавить обработку невалидного JSON.
  • Проверить все ветки ошибок.

Оператор (DevOps):

  • Настроить переменные окружения для строки подключения.
  • Добавить мониторинг и логирование.
  • Настроить ограничение ресурсов и автоскейлинг при необходимости.

Ревьювер кода:

  • Проверить обработку ошибок и очистку подключений.
  • Проверить отсутствие синхронных блокировок и утечек памяти.
  • Убедиться в наличии входной валидации.

Миграция на фреймворк: советы

  1. Выделите контроллеры: каждый блок логики маршрута переместите в отдельную функцию.
  2. Подключите middleware для парсинга JSON и обработки ошибок.
  3. Перенесите валидацию в schema-валидацию (Joi, Zod) или валидацию схемы mongoose.
  4. Покройте всё тестами перед и после миграции.

Пример команды curl для проверки

Получить список всех блогов:

curl -X GET http://localhost:3000/api/blogs

Создать блог:

curl -X POST http://localhost:3000/api/blogs -H "Content-Type: application/json" -d '{"title":"Hello","body":"World"}'

Мермайд-схема принятия решения

flowchart TD
  A[Нужен быстрый прототип?] -->|Да| B'Использовать чистый Node.js'
  A -->|Нет| C{Требования}
  C -->|Высокая производительность| D[Fastify]
  C -->|Структура и масштабируемость| E[NestJS]
  C -->|Быстрая и простая настройка| F[Express]

Часто задаваемые вопросы

Можно ли использовать этот код в продакшене?

Короткий ответ: можно, но с доработками. Добавьте валидацию, логирование, обработку ошибок, ограничение по размеру тела, настройте безопасность и мониторинг.

Почему в примере id обрабатывается как число в регулярном выражении?

В исходном примере регулярное выражение проверяло цифровой id. Для MongoDB чаще используются ObjectId (hex-строки). Проверьте формат id в вашей базе и адаптируйте регулярное выражение или используйте mongoose.Types.ObjectId.isValid(id).

Итог

Создать CRUD API на чистом Node.js вполне реально. Такой подход полезен для обучения и простых случаев. Для реальных проектов обычно выбирают фреймворк, который берет на себя рутинные задачи (пары методов, обработка ошибок, middleware). В любом случае правильная архитектура, тесты и меры безопасности остаются обязательными элементами зрелого процесса разработки.

Важно: храните конфиденциальные данные вне кода, валидируйте вход, логируйте и тестируйте.

Краткие выводы

  • Чистый Node.js даёт глубокое понимание HTTP и потоков.
  • Для продакшена пригодятся фреймворки и стандартные практики.
  • Всегда выполняйте валидацию и настройте наблюдаемость.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство