NodeJS Back-end Инженер

NodeJS Back-end Инженер

Роадмап навыков для прокачки

Базовое понимание http модуля, его назначение и особенности

Node.jsNode.js APIHttp

Основная идея

Модуль http — встроенный модуль Node.js для создания HTTP-серверов и выполнения HTTP-запросов. Это низкоуровневый API, на котором построены фреймворки Express, Koa и другие.

Ключевые аспекты

  • http.createServer() — создание HTTP-сервера
  • req (IncomingMessage) — объект входящего запроса
  • res (ServerResponse) — объект ответа сервера
  • http.request() — выполнение HTTP-запросов как клиент
  • server.listen() — запуск сервера на порту

Плюсы http модуля

  • Не требует установки дополнительных пакетов
  • Полный контроль над запросами и ответами
  • Основа для понимания работы веб-серверов
  • Поддержка HTTP/1.1 и keep-alive

Минусы

  • Низкоуровневый — много boilerplate-кода
  • Нет встроенного роутинга
  • Ручной парсинг body запроса
  • Для продакшна нужны фреймворки

Частые ошибки на собеседованиях

  • Забывают вызвать res.end() — запрос зависает
  • Не понимают, что req и res — это потоки (streams)
  • Путают http и https модули

Введение и проблематика

Модуль http позволяет Node.js работать как веб-сервер, обрабатывая входящие HTTP-запросы. Это фундаментальный модуль, на котором построены все веб-фреймворки Node.js.

Понимание http модуля важно для глубокого понимания работы Express, Koa, Fastify и других фреймворков.

Модуль http работает на основе событий (EventEmitter) и потоков (Streams), что делает его эффективным для обработки множества соединений.


Базовая теория

Основные компоненты

js
const http = require('http');
 
// Создание сервера
const server = http.createServer((req, res) => {
  // req — IncomingMessage (входящий запрос)
  // res — ServerResponse (исходящий ответ)
 
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});
 
// Запуск сервера
server.listen(3000, () => {
  console.log('Сервер запущен на http://localhost:3000');
});

Жизненный цикл запроса

sequenceDiagram participant Client participant Server participant Handler Client->>Server: HTTP Request Server->>Handler: req, res Handler->>Handler: Обработка Handler->>Server: res.end() Server->>Client: HTTP Response

Практические примеры

Создание простого сервера

Минимальный сервер

js
const http = require('http');
 
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.end('<h1>Привет, мир!</h1>');
});
 
server.listen(3000);

Обработка разных маршрутов

js
const http = require('http');
 
const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'application/json');
 
  if (req.url === '/' && req.method === 'GET') {
    res.end(JSON.stringify({ message: 'Главная страница' }));
  } else if (req.url === '/api/users' && req.method === 'GET') {
    res.end(JSON.stringify({ users: ['Иван', 'Мария'] }));
  } else {
    res.statusCode = 404;
    res.end(JSON.stringify({ error: 'Не найдено' }));
  }
});
 
server.listen(3000);

Обработка POST-запросов

js
const http = require('http');
 
const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/api/users') {
    let body = '';
 
    // req — это поток, данные приходят чанками
    req.on('data', (chunk) => {
      body += chunk.toString();
    });
 
    req.on('end', () => {
      const user = JSON.parse(body);
      res.writeHead(201, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ created: user }));
    });
  }
});
 
server.listen(3000);

Объект запроса (req)

js
const server = http.createServer((req, res) => {
  console.log('Метод:', req.method);           // GET, POST, PUT...
  console.log('URL:', req.url);                 // /path?query=value
  console.log('Заголовки:', req.headers);       // { host: '...', ... }
  console.log('HTTP версия:', req.httpVersion); // '1.1'
 
  // Парсинг URL
  const url = new URL(req.url, `http://${req.headers.host}`);
  console.log('Путь:', url.pathname);           // /path
  console.log('Query:', url.searchParams);      // URLSearchParams
 
  res.end('OK');
});

Объект ответа (res)

js
const server = http.createServer((req, res) => {
  // Установка статуса и заголовков
  res.writeHead(200, {
    'Content-Type': 'text/html',
    'X-Custom-Header': 'value'
  });
 
  // Запись данных (можно вызывать несколько раз)
  res.write('<html>');
  res.write('<body>');
  res.write('<h1>Hello</h1>');
  res.write('</body>');
  res.write('</html>');
 
  // Завершение ответа (обязательно!)
  res.end();
});

HTTP-клиент

js
const http = require('http');
 
// GET-запрос
http.get('http://api.example.com/data', (res) => {
  let data = '';
 
  res.on('data', (chunk) => {
    data += chunk;
  });
 
  res.on('end', () => {
    console.log(JSON.parse(data));
  });
}).on('error', (err) => {
  console.error('Ошибка:', err.message);
});
 
// POST-запрос
const options = {
  hostname: 'api.example.com',
  port: 80,
  path: '/users',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  }
};
 
const req = http.request(options, (res) => {
  console.log(`Статус: ${res.statusCode}`);
});
 
req.on('error', (err) => {
  console.error('Ошибка:', err.message);
});
 
req.write(JSON.stringify({ name: 'Иван' }));
req.end();

Пограничные кейсы

🚫

Обязательно вызывайте res.end()!

js
// ❌ Запрос зависнет — клиент не получит ответ
const server = http.createServer((req, res) => {
  res.write('Hello');
  // Забыли res.end()!
});
 
// ✅ Правильно
const server = http.createServer((req, res) => {
  res.end('Hello'); // end() также отправляет данные
});
⚠️

Заголовки нельзя изменить после отправки

js
const server = http.createServer((req, res) => {
  res.write('Hello');
  res.setHeader('X-Custom', 'value'); // ❌ Error!
  // Cannot set headers after they are sent
});

Обработка ошибок сервера

js
const server = http.createServer((req, res) => {
  // ...
});
 
server.on('error', (err) => {
  if (err.code === 'EADDRINUSE') {
    console.log('Порт уже занят');
  }
});
 
server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

События сервера

js
const server = http.createServer();
 
// Запрос от клиента
server.on('request', (req, res) => {
  res.end('Hello');
});
 
// Новое соединение
server.on('connection', (socket) => {
  console.log('Новое соединение');
});
 
// Сервер начал слушать
server.on('listening', () => {
  console.log('Сервер запущен');
});
 
// Сервер закрыт
server.on('close', () => {
  console.log('Сервер остановлен');
});
 
server.listen(3000);

Плюсы и минусы

Аспектhttp модульExpressFastify
УстановкаВстроенnpm installnpm install
Роутинг❌ Вручную✅ Встроен✅ Встроен
Middleware❌ Нет✅ Да✅ Да
Body parsing❌ Вручную⚠️ Плагин✅ Встроен
Производительность✅ Высокая⚠️ Средняя✅ Высокая

Вопросы интервьюера

Q: Что произойдёт, если не вызвать res.end()?

Клиент будет ждать ответа бесконечно (или до таймаута). Соединение не закроется.

Q: Чем req и res являются на низком уровне?

req — это Readable Stream, res — Writable Stream. Поэтому можно pipe() файлы напрямую в ответ.

Q: Как обработать большой POST-запрос?

Собирать данные через события 'data' и 'end', либо использовать pipe() для записи в файл.

Q: Зачем нужны фреймворки, если есть http модуль?

http модуль низкоуровневый: нет роутинга, middleware, парсинга body. Фреймворки добавляют эти абстракции.


Источники