1. Зачем нужны маршруты на сервере?
Если вы когда-нибудь пользовались браузером (а если нет — как вы вообще читаете эту лекцию?), то наверняка замечали, что сайты бывают многостраничными: у них есть главная страница, страница "О нас", список товаров, отдельные карточки товаров и так далее. Всё это — разные маршруты.
В мире серверов "маршрут" — это комбинация URL (пути, например, /, /about, /products) и HTTP-метода (GET, POST и др.). Сервер должен уметь отличать запросы друг от друга и отдавать правильные ответы.
В прошлой лекции наш сервер был похож на робота с одной репликой: "Привет, я сервер!". Сегодня мы научим его говорить разные вещи в зависимости от того, что у него спрашивают.
Базовая структура сервера с маршрутизацией
Начнём с простого примера: сервер, который умеет отвечать на три маршрута:
- / — главная страница (GET)
- /about — страница "О нас" (GET)
- /api/time — отдаёт текущее время в формате JSON (GET)
Скелет сервера
Создайте файл server.js и напишите:
const http = require('http');
const PORT = 3000;
const server = http.createServer((req, res) => {
// Здесь будет обработка маршрутов
});
server.listen(PORT, () => {
console.log(`Сервер запущен на http://localhost:${PORT}`);
});
Теперь добавим обработку маршрутов.
2. Разбор запроса: req.url и req.method
В колбэке createServer вы получаете два объекта:
- req — объект запроса, содержит информацию о том, что хочет пользователь (метод, путь, заголовки, тело)
- res — объект ответа, через который вы отправляете данные обратно
Для маршрутизации нам нужны два свойства:
- req.url — путь запроса, например, /, /about, /api/time
- req.method — HTTP-метод (GET, POST, ...)
Давайте выведем их в консоль для наглядности:
const server = http.createServer((req, res) => {
console.log('Поступил запрос:', req.method, req.url);
// ...
});
Теперь, если вы обновите страницу или зайдёте на разные адреса, увидите в консоли, какой именно запрос пришёл.
3. Простая маршрутизация: if/else
Самый прямолинейный способ обработки маршрутов — это обычные if/else if/else:
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('<h1>Главная страница</h1>');
} else if (req.method === 'GET' && req.url === '/about') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('<h1>О нас</h1><p>Это очень крутой сервер на Node.js!</p>');
} else if (req.method === 'GET' && req.url === '/api/time') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ time: new Date().toISOString() }));
} else {
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('404 Not Found');
}
});
Что здесь происходит?
- Мы проверяем метод и путь запроса.
- Для каждого маршрута отправляем свой ответ и нужный заголовок.
- Если маршрут не найден — отправляем 404.
4. Улучшаем маршрутизацию: парсинг URL
В реальных приложениях часто нужно обрабатывать параметры в URL (например, /products/42). Для этого удобно использовать модуль url или встроенный класс URL.
В Node.js 10+ можно так:
const { URL } = require('url');
const server = http.createServer((req, res) => {
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
if (req.method === 'GET' && parsedUrl.pathname === '/') {
// ...
}
// и так далее
});
Теперь вместо req.url мы используем parsedUrl.pathname — это путь без query-строки (/api/time?format=short → /api/time).
5. Пример: сервер с несколькими маршрутами
Давайте соберём всё вместе. Вот полный пример сервера с несколькими маршрутами:
const http = require('http');
const { URL } = require('url');
const PORT = 3000;
const server = http.createServer((req, res) => {
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
// Главная страница
if (req.method === 'GET' && parsedUrl.pathname === '/') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('<h1>Главная страница</h1><a href="/about">О нас</a>');
return;
}
// О нас
if (req.method === 'GET' && parsedUrl.pathname === '/about') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('<h1>О нас</h1><p>Этот сервер написан на чистом Node.js!</p><a href="/">На главную</a>');
return;
}
// API: текущее время
if (req.method === 'GET' && parsedUrl.pathname === '/api/time') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ time: new Date().toLocaleTimeString() }));
return;
}
// 404 Not Found
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('404 Not Found');
});
server.listen(PORT, () => {
console.log(`Сервер запущен на http://localhost:${PORT}`);
});
Как проверить?
- Перейдите на http://localhost:3000/ — увидите главную страницу.
- Перейдите на http://localhost:3000/about — увидите страницу "О нас".
- Перейдите на http://localhost:3000/api/time — получите JSON с текущим временем.
- Любой другой адрес даст 404.
Добавляем ещё один маршрут
В качестве практики добавим новый маршрут: /hello?name=Иван — возвращает приветствие с именем из query-параметра.
Как это сделать?
- Парсим query-параметры через parsedUrl.searchParams.
- Получаем значение параметра name.
if (req.method === 'GET' && parsedUrl.pathname === '/hello') {
const name = parsedUrl.searchParams.get('name') || 'Гость';
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(`<h1>Привет, ${name}!</h1>`);
return;
}
Теперь если вы откроете http://localhost:3000/hello?name=Иван, сервер ответит: "Привет, Иван!".
6. Кратко о поддержке разных методов (GET, POST)
Пока что мы обрабатывали только GET-запросы. Если хотите добавить поддержку, например, POST-запроса на /api/echo, можно сделать так:
if (req.method === 'POST' && parsedUrl.pathname === '/api/echo') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ youSent: body }));
});
return;
}
Теперь можно отправить POST-запрос с помощью Postman или curl, и сервер вернёт то, что вы ему отправили.
7. Типичные ошибки при создании простого сервера с маршрутами
Ошибка №1: не учитывается query-строка.
Если сравнивать req.url напрямую с /about, а пользователь зашёл на /about?utm=123, такой запрос не обработается. Лучше всегда использовать parsedUrl.pathname.
Ошибка №2: забыли выставить правильный Content-Type.
Если отправить JSON, но не указать application/json, браузер может не понять, что это. Аналогично с HTML.
Ошибка №3: не завершён ответ (res.end).
Если забыть вызвать res.end(), браузер будет ждать ответа вечно, а сервер может зависнуть.
Ошибка №4: не обрабатываются методы кроме GET.
Если сервер должен поддерживать POST, PUT и др., не забывайте проверять req.method.
Ошибка №5: дублирование кода при большом количестве маршрутов.
Для 2-3 маршрутов if/else достаточно, но если их становится больше — код становится нечитаемым. Для сложных серверов лучше использовать фреймворки (например, Express.js), но для учебных целей if/else — отличный старт.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ