Node.js Express: маршрут не срабатывает и возвращает 404 Not Found

mr. Cooper 1 час назад Веб-разработка
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 искал другой маршрут.

Комментарии

Пока нет комментариев. Будьте первым, кто напишет.

Чтобы оставить комментарий, войдите в аккаунт.

Похожие статьи