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

Как создать простое API на Node.js без фреймворка

7 min read Backend Node.js Обновлено 08 Apr 2026
Создать API на Node.js без фреймворка
Создать API на Node.js без фреймворка

человек держит наклейку Node.js

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

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

Большинство API на Node.js используют Express или другой фреймворк. Тем не менее, вы можете создать простое API на Node.js без фреймворка за несколько шагов. Это полезно для обучения, для минималистичных сервисов или когда вы хотите иметь полный контроль над поведением HTTP-сервера.

Что вы получите из этой статьи

  • Пошаговая инструкция по созданию CRUD API для блога на Node.js и MongoDB без фреймворка.
  • Готовые фрагменты кода для server.js, модели Mongoose и маршрутизации.
  • Практические рекомендации по безопасности, тестированию и переходу на фреймворк.
  • Чек-листы для разработчика, команды QA и DevOps.

Шаг 1: Подготовка окружения разработки

Создайте папку проекта и перейдите в неё:

mkdir nodejs-api  
cd nodejs-api  

Инициализируйте npm:

npm init -y  

В этом примере CRUD API использует MongoDB (NoSQL) и популярный ODM — mongoose.

Установите mongoose:

npm install mongoose  

Создайте файл server.js в корне проекта и добавьте код ниже, чтобы запустить базовый HTTP-сервер:

const http = require("http");
const server = http.createServer((req, res) => {});
  
server.listen(3000, () => {
  console.log(`Server is running`);
});

Пояснение: код импортирует встроенный модуль http, создаёт сервер и запускает прослушивание порта 3000. В callback createServer доступны объекты req и res для обработки запросов.

Создайте две папки в корне проекта: routes и models. Папка routes будет содержать логику маршрутизации, а models — определения схем и моделей для базы данных.

Important: для production используйте переменные окружения для порта и URI MongoDB (пример ниже).

Шаг 2: Подключение к базе данных

В server.js импортируйте mongoose:

const mongoose = require("mongoose");

Вызовите connect с вашим URI MongoDB:

mongoose.connect("MongoDB_URI")

Рекомендация: храните строку подключения в переменной окружения, например process.env.MONGODB_URI, и передавайте опции соединения (useNewUrlParser, useUnifiedTopology и т.д.) по необходимости.

Пример безопасного подключения с таймаутом и обработкой ошибок:

const uri = process.env.MONGODB_URI || "mongodb://localhost:27017/mydb";
mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('MongoDB connected'))
  .catch(err => console.error('MongoDB connection error', err));

Шаг 3: Создание модели API

Создадим API CRUD для простого блога. В папке 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: ODM для MongoDB, упрощающий работу со схемами и моделями.
  • Schema: определение структуры документа в коллекции.
  • Model: объект для CRUD-операций (find, findById, save и т.д.).

Шаг 4: Реализация маршрутизации вручную

Без фреймворков вроде Express нужно самостоятельно проверять URL и метод и обрабатывать поток запроса/ответа.

Создайте routes/blogRoutes.js и импортируйте модель:

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

Далее создайте асинхронную функцию router и экспортируйте её:

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

Эта функция будет содержать всю логику маршрутизации. Ниже приведены обработчики для каждого маршрута. Сохраняйте существующие блоки кода и вставляйте их внутрь router.

GET — получить все записи

Добавьте в router:

//  GET: /api/blogs
if (req.url === "/api/blogs" && req.method === "GET") {
    // get all blogs
    const blogs = await Blog.find();
  
    // set the status code and content-type
    res.writeHead(200, { "Content-Type": "application/json" });
  
    // send data
    res.end(JSON.stringify(blogs));
}

Пояснение: проверяем req.url и req.method, получаем все документы через Blog.find(), устанавливаем заголовки и возвращаем JSON.

GET — получить одну запись

Добавьте:

// GET: /api/blogs/:id
if (req.url.match(/\/api\/blogs\/([0-9]+)/) && req.method === "GET") {
    try {
        // extract id from url
        const id = req.url.split("/")[3];
  
        // get blog from DB
        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 }));
    }
}

Пояснение: используем regex через match, извлекаем id из URL методом split, затем ищем по id и возвращаем запись либо ошибку 404.

Note: текущая регулярка предполагает числовой id в URL. Если вы используете ObjectId MongoDB (строка 24 символа), адаптируйте шаблон: /\/api\/blogs\/([a-fA-F0-9]{24})/.

POST — создать запись

Добавьте:

// POST: /api/blogs/
if (req.url === "/api/blogs" && req.method === "POST") {
    try {
        let body = "";
  
        // Listen for data event
        req.on("data", (chunk) => {
            body += chunk.toString();
        });
  
        // Listen for end event
        req.on("end", async () => {
            // Create Blog
            let blog = new Blog(JSON.parse(body));
  
            // Save to DB
            await blog.save();
            res.writeHead(200, { "Content-Type": "application/json" });
            res.end(JSON.stringify(blog));
        });
    } catch (error) {
        console.log(error);
    }
}

Пояснение: req — поток ReadableStream: слушаем события data и end, собираем тело запроса, парсим JSON и сохраняем документ.

Совет: добавьте проверку Content-Type и ограничение на размер тела, чтобы избежать DoS-атак.

PUT — обновить запись

Добавьте:

// PUT: /api/blogs/:id
if (req.url.match(/\/api\/blogs\/([0-9]+)/) && req.method === "PUT") {
    try {
        // extract id from url
        const id = req.url.split("/")[3];
        let body = "";
  
        req.on("data", (chunk) => {
            body += chunk.toString();
        });

        req.on("end", async () => {
            // Find and update document
            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);
    }
}

Пояснение: поведение аналогично POST, но дополнительно извлекается id для обновления.

DELETE — удалить запись

Добавьте:

// DELETE: /api/blogs/:id
if (req.url.match(/\/api\/blogs\/([0-9]+)/) && req.method === "DELETE") {
    try {
        const id = req.url.split("/")[3];
          
       // Delete blog from DB
        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 }));
    }
}

Пояснение: извлекаем id и вызываем findByIdAndDelete, затем возвращаем сообщение об успешном удалении.

Подключение router в server.js

Импортируйте и используйте router в server.js:

const router = require("./routes/blogRoutes");
  
const server = http.createServer((req, res) => {
    router(req, res);
});

Теперь ваш сервер перехватывает входящие запросы и обрабатывает их через router-функцию.

Примечание: в коде выше все роуты проверяются последовательно; в крупных проектах это неэффективно — рассмотрите минимальную оптимизацию (маршрутизация по префиксу, быстрые сравнения и т.д.).

Практические дополнения: безопасность, тесты, мониторинг

Security hardening — рекомендации:

  • Валидация входных данных: используйте схемы и дополнительную проверку данных (например, Joi, validator) до сохранения в БД.
  • Ограничение размера тела запроса: проверяйте длину накопленного body и прерывайте соединение, если превышено безопасное значение.
  • Защита от инъекций: Mongoose помогает, но всё равно фильтруйте поля, которые принимаете от пользователя.
  • HTTPS: в продакшне используйте TLS (reverse-proxy или встроенные сертификаты).
  • Логи и аудит: логируйте ошибочные запросы, время выполнения и исключения.

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

Критерии приёмки:

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

Тест-кейсы (минимум):

  • Успешное создание с title и body.
  • Ошибка при создании без title.
  • Получение несуществующего id -> 404.
  • Попытка отправить большое тело (> допустимого) -> 413 или закрытие соединения.

Мониторинг и SLI/SLO (рекомендации):

  • Метрики: время отклика 95-го перцентиля, количество ошибок 5xx в минуту.
  • Логи: структурированный JSON лог (timestamp, path, method, status, duration).

Когда реализация вручную не подходит

Counterexamples / ограничения:

  • Если приложение быстро растёт и требует middlewares, валидации, роутинга с параметрами и плагинов — лучше использовать Express, Fastify или NestJS.
  • Для микросервисов с высокой нагрузкой полезны фреймворки с поддержкой плагинов и быстрым маршрутизатором.
  • Если вам нужна готовая экосистема middlewares (авторизация, CORS, rate-limiting), реализация вручную потребует много времени и тестов.

Альтернативы и миграция на фреймворк

Краткая матрица выбора:

  • Маленький сервис, образовательный проект — ручной HTTP-сервер.
  • API средней сложности — Fastify (производительность) или Express (экосистема).
  • Сложная архитектура, DDD/DI — NestJS.

Миграционные советы:

  • Выделите слой контроллеров и сервисов, чтобы изолировать бизнес-логику от конкретной реализации HTTP.
  • Напишите адаптеры: текущая router-функция может оборачивать контроллеры, которые позже будут подключены к Express.

Mermaid-дерево принятия решения:

flowchart TD
  A[Нужен API] --> B{Небольшой проект?}
  B -- Да --> C[Ручной Node.js HTTP]
  B -- Нет --> D{Нужна экосистема и плагины?}
  D -- Да --> E[Express или Fastify]
  D -- Нет --> F[NestJS]

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

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

  • Храню конфигурацию в env-переменных.
  • Ограничил размер тела запроса.
  • Валидирую входящие данные.
  • Логирую все исключения.

QA:

  • Тесты на создание/чтение/обновление/удаление.
  • Негативные тесты (пустые поля, некорректные id).
  • Нагрузочные тесты на ожидаемую частоту запросов.

DevOps:

  • Мониторинг и алёрты на ошибки 5xx.
  • Автоматический деплой с миграциями БД при необходимости.
  • Резервное копирование базы данных.

SOP: Быстрый план развертывания (мини-playbook)

  1. Настроить переменные окружения: MONGODB_URI, PORT, NODE_ENV.
  2. Убедиться, что MongoDB доступен и настроен доступ (файрволлы, сетевые правила).
  3. Запустить npm install и затем node server.js или pm2/forever.
  4. Проверить эндпоинты через curl или Postman.
  5. Установить мониторинг и резервное копирование.

Пример сниппетов и конфигов

Фрагмент: подключение mongoose с обработкой ошибок и graceful shutdown:

const mongoose = require('mongoose');
const uri = process.env.MONGODB_URI;

mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('MongoDB connected'))
  .catch(err => console.error('MongoDB connection error', err));

process.on('SIGINT', async () => {
  await mongoose.connection.close();
  console.log('MongoDB connection closed due to app termination');
  process.exit(0);
});

Parsing JSON safely (стример): добавьте проверку Content-Type и try/catch при JSON.parse:

if (req.headers['content-type'] !== 'application/json') {
  res.writeHead(415, { 'Content-Type': 'application/json' });
  return res.end(JSON.stringify({ message: 'Unsupported Media Type' }));
}

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

req.on('end', async () => {
  try {
    const data = JSON.parse(body);
    // обработка data
  } catch (err) {
    res.writeHead(400, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'Invalid JSON' }));
  }
});

Советы по переходу на Express (миграционные шаги)

  1. Создайте контроллеры, которые принимают (req, res) и вызывают сервисы. Вручную реализованные обработчики можно перенести в эти контроллеры.
  2. Добавьте маршрутный слой Express и подключите контроллеры к маршрутам.
  3. Используйте body-parser (встраивается в express) и middleware для валидации/авторизации.

Конфликты и типичные ошибки (edge-case gallery)

  • Некорректный формат id: MongoDB ObjectId не соответствует числовому шаблону в регулярке — используйте адаптивную проверку.
  • Повторный вызов res.end: следите, чтобы поток данных обрабатывался один раз.
  • Длинные асинхронные операции без таймаута блокируют цикл событий — используйте асинхронность и профилируйте.

Заключение

Реализация API на Node.js без фреймворка даёт полный контроль и помогает понять, как работает HTTP-сервер и потоки в Node.js. Однако по мере роста проекта стоит оценить затраты на поддержку и рассмотреть переход на фреймворк.

Вы можете найти завершённый проект в соответствующем репозитории GitHub.

Важно: этот подход хорош для обучения и простых сервисов. Для продакшена учитывайте требования безопасности, тестирования и поддерживаемости.

Краткое резюме:

  • Начните с минимального HTTP-сервера и подключите mongoose.
  • Реализуйте CRUD-логики через проверку url и method.
  • Добавьте валидацию, ограничение размера запроса и обработку ошибок.
  • Планируйте миграцию на фреймворк при увеличении сложности.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

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

CHKDSK в Windows 10 — как проверить и исправить диск
Windows 10

CHKDSK в Windows 10 — как проверить и исправить диск

Искать файлы Google Drive из адресной строки Chrome
Советы

Искать файлы Google Drive из адресной строки Chrome

Credential Manager в Windows — управление паролями
Безопасность

Credential Manager в Windows — управление паролями

MailTrack в Opera: узнавайте когда прочли письма
Productivity

MailTrack в Opera: узнавайте когда прочли письма

Простой калькулятор на Python с Tkinter
Python GUI

Простой калькулятор на Python с Tkinter

Как найти клиентов на Facebook — практическое руководство
Маркетинг

Как найти клиентов на Facebook — практическое руководство