Не удается найти модуль: отладка импортов Express и параметров маршрута
API погоды на TypeScript Express не работает из-за отсутствия импорта контроллера. Проверьте пути, регистр букв и операторы экспорта, а также убедитесь в корректности значения :city перед вызовом API погоды.
Отладка API погоды на TypeScript с использованием Express часто помогает лучше понять, как взаимодействуют компоненты серверной части на Node, чем любой другой абстрактный пример работы с REST.
Ошибка
После того как проект был разделен на маршруты и контроллеры, сервер выдал ошибку:
Cannot find module '../controllers/weatherController'
Такое сообщение часто появляется, когда серверная часть состоит из нескольких файлов. Express пытается импортировать модуль weatherController, но не может найти его по указанному пути.
Что проверить
Необходимо последовательно разбираться с причиной ошибки, а не догадываться.
Существует ли файл? В папке src/ должны находиться соответствующие файлы.
src/
├── controllers/
│ └── weatherController.ts
Совпадает ли название папки точно? Лучше использовать название controllers, а не controller или Controllers. На файловых системах с чувствительностью к регистру важны как форма множественного числа, так и регистр букв.
Совпадает ли имя файла полностью? Ожидается weatherController.ts, а не WeatherController.ts, weathercontroller.ts или weather-controller.ts. Механизм разрешения модулей TypeScript рассматривает их как отдельные цели.
Правильен ли путь импорта? В файле weatherRoutes.ts структура импортов и связывания маршрутов выглядит следующим образом:
import { Router } from "express";
import { getWeather } from "../controllers/weatherController";
const router = Router();router.get("/:city", getWeather);export default router;
Экспортирует ли контроллер действительно функцию? Частой ошибкой является написание обработчика без ключевого слова export, из-за чего импорт маршрута не имеет чего бы связать.
import { Request, Response } from "express";
export const getWeather = (req: Request, res: Response): void => {
const { city } = req.params; res.json({
city,
temperature: 29,
condition: "Cloudy",
humidity: 82,
});
};
Добавление export перед функцией getWeather восстановило работу сервера. Одно ключевое слово — одна устраненная ошибка.
Что уже реализовано в проекте
На этом этапе сервис прогнозирования погоды уже включает несколько элементов, соответствующих стандартам производства:
- Инструментарий TypeScript для репозитория
- Запущенный процесс Express HTTP
- Отдельные директории вместо одного огромного файла
- Маршрутизаторы URL, связанные с обработчиками
- Модули-обработчики для логики запросов
- Параметры пути, такие как
:city - Структурированные JSON-данные
- Устранение ошибок отсутствующих модулей путем проверки путей и экспортов локально
Понимание параметров маршрутов
Испробуйте маршрут с помощью HTTP-клиента, такого как Postman:
GET http://localhost:3000/weather/bangalore
GET http://localhost:3000/weather/mumbai
GET http://localhost:3000/weather/chennai
Каждый ответ изменяет поле city. Структура остаётся неизменной: поля temperature, condition и humidity по-прежнему присутствуют. Значение города поступает из сегмента пути после /weather/ и доступно через req.params.city. Именно в этом заключается смысл параметра маршрута: одно определение маршрута, но несколько значений, подставляемых при каждом запросе.
Следующая задача: базовая валидация
Запрос, похожий на приведённый ниже, всё ещё будет успешным с использованием фиктивных данных:
GET /weather/123
и вернёт что-то вроде:
{
"city": "123",
"temperature": 29,
"condition": "Cloudy",
"humidity": 82
}
Название города не должно состоять только из цифр. В таком случае следует отклонить запрос, а не считать его допустимым входным данныем для прогноза погоды.
Проверьте параметр города с помощью регулярного выражения /^\d+$/. При полном совпадении ответьте кодом 400 Bad Request, а не фиктивными данными о погоде:
res.status(400).json({
error: "City name must contain letters.",
});
В противном случае возвращайте обычный пакет данных о погоде. Реализация проверки перед поиском готового ответа позволяет лучше соблюдать правила, чем простое вставление готового фрагмента кода.
Краткая проверка знаний
Короткий тест помогает подтвердить понимание материала, а не просто гарантировать успешную компиляцию:
1. В чем GET /weather/:city отличается от GET /weather?city=bangalore? Сегменты пути используют обязательные параметры маршрута, встроенные в шаблон URL. Значения после ? являются параметрами запроса и остаются необязательными. Используйте параметры маршрута, когда значение служит идентификатором ресурса; используйте параметры запроса для фильтров и переключателей.
2. Зачем использовать res.status(400).json() вместо простого res.json()? По умолчанию res.json() возвращает код 200 OK. Для некорректных данных необходим код статуса, указывающий на сбой, а не только строка с ошибкой в теле запроса. Клиенты опираются именно на этот код.
3. Какой слой отвечает за бизнес-правила — роутер, контроллер или что-то ещё? Правила следует размещать в контроллерах. Роутеры лишь связывают метод HTTP и путь с обработчиком. Валидация и внешние вызовы сначала выполняются в контроллере, а затем переносятся в модуль сервиса по мере роста приложения.
4. Что содержит req.params.city для запроса GET /weather/mumbai? Строка "mumbai", точно такая, как она указана в пути.
Что будет дальше
Модель погоды выполнила свою функцию. Следующим слоем является API реальной погоды, что подразумевает:
- Обращение к внешнему HTTP API
- Использование переменных окружения (
.env), чтобы ключи никогда не были закодированы напрямую в исходном коде - Использование
fetchилиaxiosдля отправки запросов - Адекватная обработка тайм-аутов и сбоев на стороне сервера
- Наличие папки с сервисами, чтобы контроллеры не занимались работой с HTTP-клиентом
Путь запроса получает ещё один уровень:
Browser/Postman
│
▼
Routes
│
▼
Controllers
│
▼
Services
│
▼
Weather API (external)
Такая структура с несколькими уровнями характерна для многих производственных приложений на Express. Поэтапная разработка позволяет иметь чёткую точку отката в случае сбоя при интеграции с реальными данными, вместо того чтобы объединять все функции в один файл.