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.
Если маршрутов становится много, код начинает напоминать лабиринт. Это сигнал: пора подумать о рефакторинге или использовать фреймворк.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ