JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Ручная маршрутизация в Node.js

Ручная маршрутизация в Node.js

Модуль 4: Node.js, Next.js и Angular
4 уровень , 5 лекция
Открыта

1. Зачем нужна маршрутизация?

Маршрутизация — это процесс определения, какой код (или функция) на сервере должен обработать конкретный HTTP-запрос.

Пример из жизни:
Вы пришли в супермаркет и говорите "Где отдел овощей?". Если бы у магазина не было маршрутизации, вы бы попадали всегда только в отдел сладостей. Не самая здоровая диета для вашего приложения!

В веб-сервере маршрутизация помогает:

  • Отвечать разными страницами на разные URL (например, /about, /contacts, /products).
  • Реализовывать REST API: разные методы (GET, POST, PUT, DELETE) для одного и того же адреса.
  • Разделять логику обработки разных маршрутов.

Как это выглядит в Node.js?

В Node.js, если мы не используем никакие фреймворки (Express, Koa и т.п.), маршрутизацию приходится делать вручную — с помощью обычных конструкций if/else или switch.


const http = require('http');

const server = http.createServer((req, res) => {
  // Здесь мы будем писать нашу маршрутизацию!
});

server.listen(3000, () => {
  console.log('Сервер запущен на http://localhost:3000');
});

2. Маршрутизация с помощью if/else

Начнем с самого простого и наглядного способа — цепочки if/else.

Пример: разные ответы для разных URL


const http = require('http');

const server = http.createServer((req, res) => {
  if (req.url === '/' && req.method === 'GET') {
    res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
    res.end('<h1>Главная страница</h1>');
  } else if (req.url === '/about' && req.method === 'GET') {
    res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
    res.end('<h1>О сайте</h1>');
  } else if (req.url === '/contacts' && req.method === 'GET') {
    res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
    res.end('<h1>Контакты</h1>');
  } else {
    res.writeHead(404, {'Content-Type': 'text/html; charset=utf-8'});
    res.end('<h1>404 Не найдено</h1>');
  }
});

server.listen(3000, () => {
  console.log('Сервер запущен на http://localhost:3000');
});

Что происходит?
Если пользователь обращается по адресу /, сервер возвращает "Главная страница".
Если по /about — "О сайте".
Если по /contacts — "Контакты".
Всё остальное — 404.

Разбираем детали

  • req.url — это путь, который запросил клиент (например, /about).
  • req.method — HTTP-метод (GET, POST, и т.д.).
  • Мы сравниваем оба значения, чтобы быть точными (например, не обрабатывать POST /about как обычную страницу).

3. Маршрутизация с помощью switch

Если маршрутов становится много, цепочка if/else начинает напоминать спагетти-код. Тут на помощь приходит конструкция switch.

Пример: обработка только по URL


const http = require('http');

const server = http.createServer((req, res) => {
  switch (req.url) {
    case '/':
      res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
      res.end('<h1>Главная страница</h1>');
      break;
    case '/about':
      res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
      res.end('<h1>О сайте</h1>');
      break;
    case '/contacts':
      res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
      res.end('<h1>Контакты</h1>');
      break;
    default:
      res.writeHead(404, {'Content-Type': 'text/html; charset=utf-8'});
      res.end('<h1>404 Не найдено</h1>');
  }
});

server.listen(3000, () => {
  console.log('Сервер запущен на http://localhost:3000');
});

Внимание!
В этом примере мы не различаем методы (GET, POST и т.д.). Если нужно — можно сделать вложенный switch или добавить дополнительные проверки.

Пример: switch по методу


const http = require('http');

const server = http.createServer((req, res) => {
  switch (req.method) {
    case 'GET':
      // обработка GET-запросов
      break;
    case 'POST':
      // обработка POST-запросов
      break;
    default:
      res.writeHead(405, {'Content-Type': 'text/html; charset=utf-8'});
      res.end('<h1>Метод не поддерживается</h1>');
  }
});

server.listen(3000, () => {
  console.log('Сервер запущен на http://localhost:3000');
});

Пример: комбинированный switch/if

Можно вложить switch в if, или наоборот, если хочется поиграть в "архитектора года":


if (req.url === '/api/user') {
  switch (req.method) {
    case 'GET':
      // вернуть пользователя
      break;
    case 'POST':
      // создать пользователя
      break;
    default:
      // ошибка
  }
}

4. Практика: добавляем API в наше мини-приложение

Продолжаем строить наше учебное приложение!
Допустим, у нас есть список задач (TODO), который пока просто хранится в массиве:


let todos = [
  { id: 1, text: 'Купить хлеб' },
  { id: 2, text: 'Почитать лекцию' },
];

Добавим маршрутизацию для двух маршрутов:

  • GET /api/todos — возвращает список задач в формате JSON.
  • POST /api/todos — добавляет новую задачу (данные будут приходить в теле запроса, пока не реализуем).

Шаг 1. Обработка GET-запроса


const http = require('http');

let todos = [
  { id: 1, text: 'Купить хлеб' },
  { id: 2, text: 'Почитать лекцию' },
];

const server = http.createServer((req, res) => {
  if (req.url === '/api/todos' && req.method === 'GET') {
    res.writeHead(200, {'Content-Type': 'application/json; charset=utf-8'});
    res.end(JSON.stringify(todos));
  } else {
    res.writeHead(404, {'Content-Type': 'text/html; charset=utf-8'});
    res.end('<h1>404 Не найдено</h1>');
  }
});

server.listen(3000, () => {
  console.log('Сервер запущен на http://localhost:3000');
});

Теперь если вы откроете http://localhost:3000/api/todos — увидите массив задач в JSON.

Шаг 2. Обработка POST-запроса (простая заглушка)


if (req.url === '/api/todos' && req.method === 'POST') {
  res.writeHead(201, {'Content-Type': 'application/json; charset=utf-8'});
  res.end(JSON.stringify({ message: 'Задача добавлена (заглушка)' }));
}

Внимание:
Чтение тела POST-запроса — отдельная тема, мы к ней вернемся позже. Сейчас просто имитируем добавление.

Итоговый пример с двумя маршрутами


const http = require('http');

let todos = [
  { id: 1, text: 'Купить хлеб' },
  { id: 2, text: 'Почитать лекцию' },
];

const server = http.createServer((req, res) => {
  if (req.url === '/api/todos' && req.method === 'GET') {
    res.writeHead(200, {'Content-Type': 'application/json; charset=utf-8'});
    res.end(JSON.stringify(todos));
  } else if (req.url === '/api/todos' && req.method === 'POST') {
    res.writeHead(201, {'Content-Type': 'application/json; charset=utf-8'});
    res.end(JSON.stringify({ message: 'Задача добавлена (заглушка)' }));
  } else {
    res.writeHead(404, {'Content-Type': 'text/html; charset=utf-8'});
    res.end('<h1>404 Не найдено</h1>');
  }
});

server.listen(3000, () => {
  console.log('Сервер запущен на http://localhost:3000');
});

5. Полезные нюансы

Особенности: сравнение if/else и switch

if/else — удобно для сложных условий, когда нужно проверять сразу и URL, и метод, или использовать регулярные выражения.
switch — лучше подходит, если проверяете только один параметр (например, только URL или только метод).

Если маршрутов становится очень много, код начинает разрастаться и терять читаемость. В реальных проектах на Node.js маршрутизацию обычно делегируют фреймворкам (например, Express), но ручная маршрутизация — отличный способ понять, как всё работает "под капотом".

Поддержка динамических маршрутов

Пока мы умеем отвечать только на "жёстко заданные" адреса. А как быть, если нужно, например, обработать /api/todos/42 (где 42 — id задачи)?

Для этого можно использовать простейший парсинг строки:


if (req.url.startsWith('/api/todos/') && req.method === 'GET') {
  const id = req.url.split('/').pop();
  // Находим задачу по id
  const todo = todos.find(item => item.id === Number(id));
  if (todo) {
    res.writeHead(200, {'Content-Type': 'application/json; charset=utf-8'});
    res.end(JSON.stringify(todo));
  } else {
    res.writeHead(404, {'Content-Type': 'application/json; charset=utf-8'});
    res.end(JSON.stringify({ error: 'Задача не найдена' }));
  }
}

В реальных проектах для разбора URL используют модуль url или класс URL, о которых поговорим на следующей лекции.

6. Типичные ошибки при ручной маршрутизации

Ошибка №1: Не учитывается метод запроса.
Очень часто новички пишут только if (req.url === '/api/todos'), не различая GET и POST. В итоге сервер может возвращать не тот ответ, который ожидался, или вообще ломаться при попытке обработать неожиданный метод.

Ошибка №2: Ошибки в сравнении строк.
Иногда браузер автоматически добавляет слэш в конце (/about/ вместо /about). Ваша проверка req.url === '/about' не сработает, и вы получите 404. Можно использовать req.url.startsWith('/about') или убирать слэш вручную.

Ошибка №3: Отсутствие обработки 404.
Если не добавить обработку "по умолчанию", сервер просто не ответит, и браузер будет ждать вечно. Всегда добавляйте блок, который возвращает 404 для неизвестных адресов.

Ошибка №4: Путаница с Content-Type.
Если вы отправляете JSON, всегда указывайте правильный заголовок: Content-Type: application/json; charset=utf-8. Если забыть — браузер может не понять ответ.

Ошибка №5: Сложные вложенные if/else.
Если маршрутов становится много, код начинает напоминать лабиринт. Это сигнал: пора подумать о рефакторинге или использовать фреймворк.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 4 уровень, 5 лекция
Недоступна
Маршрутизация простых страниц с помощью if/else
Маршрутизация простых страниц с помощью if/else
1
Задача
Модуль 4: Node.js, Next.js и Angular, 4 уровень, 5 лекция
Недоступна
Использование switch для обработки разных адресов
Использование switch для обработки разных адресов
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ