Node.js Express: маршрут не срабатывает и возвращает 404 Not Found
Express возвращает 404 Not Found, хотя маршрут в Node.js вроде бы написан правильно? Если в браузере появляется Cannot GET /users, это ещё не означает, что ошибка находится внутри app.get().
Например, маршрут выглядит совершенно нормально:
//js
app.get('/users', (req, res) => {
res.json({ message: 'Users list' });
});Сервер запускается без ошибок, приложение работает, но запрос всё равно заканчивается 404. В такой ситуации я бы не стал сразу менять код обработчика. Для начала стоит посмотреть, что именно пришло в Express и какой путь приложение действительно зарегистрировало.
Особенно часто это становится заметно после того, как маршруты начинают выносить в отдельные Router.
Сначала проверьте HTTP-метод
Самая простая причина - запрос отправляется не тем методом.
Например:
//js
app.get('/users', (req, res) => {
res.json({ message: 'Users list' });
});Этот маршрут отвечает только на:
//text
GET /usersЕсли отправить:
//text
POST /usersобработчик вообще не запустится.
На практике такая ошибка легко появляется с формами или API-клиентами. В коде разработчик видит app.get() и проверяет /users в браузере, хотя другой клиент в этот момент отправляет туда POST.
Если есть сомнения, можно на время поставить простой middleware:
//js
app.use((req, res, next) => {
console.log(req.method, req.originalUrl);
next();
});После запроса в терминале появится, например:
//text
GET /usersили:
//text
POST /usersИногда этого уже достаточно, чтобы понять причину.
С Router путь легко собрать неправильно
Допустим, роутер вынесен в отдельный файл:
//js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.json({ message: 'Users list' });
});
module.exports = router;В основном файле он подключается так:
//js
const usersRouter = require('./routes/users');
app.use('/users', usersRouter);Здесь всё выглядит немного непривычно, но итоговый URL получается /users.
app.use('/users', usersRouter) задаёт первую часть пути, а router.get('/') - путь внутри самого роутера.
Поэтому запрос:
//text
GET /usersпопадёт в этот обработчик.
А если внутри написать:
//js
router.get('/users', (req, res) => {
// ...
});путь уже будет другим:
//text
/users/usersИ запрос к /users закончится 404.
В небольшом примере это сразу видно. В проекте с десятком роутеров и несколькими файлами уже приходится мысленно собирать путь из нескольких мест.
Префикс /api тоже является частью маршрута
Например, приложение подключает роутер так:
//js
app.use('/api', usersRouter);а внутри находится:
//js
router.get('/users', (req, res) => {
res.json({ message: 'Users list' });
});Получается:
//text
GET /api/usersЗапрос к:
//text
GET /usersсюда не попадёт.
На это особенно легко не обратить внимание, если /api добавляется один раз при подключении общего роутера:
//js
app.use('/api', apiRouter);После этого внутри отдельных файлов разработчик работает с /users, /posts, /comments и постепенно перестаёт держать в голове полный URL.
Поэтому при 404 полезно смотреть не только на router.get(), но и на строку, где этот роутер подключается.
Сам Router ничего не делает, пока его не зарегистрировали
Есть и совсем простая ситуация:
//js
const router = express.Router();
router.get('/users', (req, res) => {
res.json({ message: 'Users list' });
});Маршрут здесь действительно создан. Но само существование переменной router не добавляет его в Express.
Нужна регистрация:
//js
app.use(router);или, например:
//js
app.use('/api', router);Во втором случае маршрут станет /api/users.
Такое довольно легко получить после рефакторинга. Раньше всё находилось в app.js, потом маршруты вынесли в routes/users.js, добавили module.exports, импортировали файл - и где-то между этими действиями забыли app.use().
При этом Node.js не обязательно сообщит об ошибке. Сервер спокойно запустится. Просто нужного маршрута в приложении не окажется.
Проверьте порядок middleware
Express идёт по middleware сверху вниз.
Поэтому порядок этих двух частей имеет значение:
//js
app.use('/api', usersRouter);
app.use((req, res) => {
res.status(404).send('Not Found');
});Сначала Express проверит usersRouter. Если подходящего маршрута там нет, запрос продолжит движение и попадёт в обработчик 404.
Если поменять их местами:
//js
app.use((req, res) => {
res.status(404).send('Not Found');
});
app.use('/api', usersRouter);
до usersRouter запрос уже не дойдёт. Первый обработчик завершит ответ.
Это одна из причин, почему собственный обработчик 404 обычно ставят в самом конце:
//js
app.use('/users', usersRouter);
app.use('/posts', postsRouter);
app.use((req, res) => {
res.status(404).json({
error: 'Route not found'
});
});Если запрос дошёл до последнего блока, значит предыдущие обработчики его не забрали.
Иногда проще посмотреть на сам запрос
Бывает ситуация, когда маршрут несколько раз проверен, Router подключён, метод вроде бы правильный - а 404 всё равно остаётся.
В этот момент я бы уже не перебирал строки с router.get(). Проще посмотреть, что реально получает Express:
//js
app.use((req, res, next) => {
console.log({
method: req.method,
url: req.originalUrl
});
next();
});Например, в консоли можно увидеть:
//text
{
method: 'GET',
url: '/api/users?page=2'
}А проверять при этом /users.
req.originalUrl удобен ещё и тем, что сохраняет исходный URL вместе с префиксом и query string.
Можно поставить такой вывод непосредственно перед подключением роутера:
//js
app.use((req, res, next) => {
console.log(req.method, req.originalUrl);
next();
});
app.use('/api', usersRouter);Если в терминале появляется:
//text
GET /usersа роутер подключён через /api, дальше уже понятно, куда смотреть.
Не забудьте про параметры маршрута
Маршрут:
//js
app.get('/users/:id', (req, res) => {
res.json({
id: req.params.id
});
});ожидает URL с параметром:
//text
/users/42А запрос:
//text
/usersему не соответствует.
То же самое с более длинным маршрутом:
//js
app.get('/users/:id/posts/:postId', (req, res) => {
res.json(req.params);
});Здесь нужен полный путь, например:
//text
/users/42/posts/7Если обработчик не был вызван, искать проблему в req.params пока бессмысленно. До этой части кода Express ещё не дошёл.
Что проверить, если /users внезапно стал отдавать 404
Я обычно начинаю не с самого обработчика, а с маршрута целиком.
Допустим, есть:
//js
app.use('/api', usersRouter);и внутри:
//js
router.get('/users', handler);Значит, конечный адрес:
//text
GET /api/usersЕсли же внутри стоит:
//js
router.post('/users', handler);то открыть этот адрес в браузере недостаточно - браузер отправит GET, а обработчик ждёт POST.
И только после этого имеет смысл смотреть, что происходит внутри handler.
У Express сообщение Cannot GET /users в этом плане даже полезнее, чем кажется. Оно показывает метод и URL, с которыми пришёл запрос. Дальше нужно сравнить их с тем, как реально собраны app.use() и router.get() в приложении.
Очень часто оказывается, что сам обработчик был написан нормально. Просто Express искал другой маршрут.
Комментарии
Чтобы оставить комментарий, войдите в аккаунт.