无法找到模块:调试 Express 导入及路由参数
由于缺少控制器导入,TypeScript Express天气API无法正常运行。请检查路径、大小写及导出设置,并在调用实时天气API之前验证城市名称是否正确。
调试 TypeScript + Express 构建的天气 API,往往比其他抽象的 REST 开发教程更能让人了解 Node 后端各组件的协同方式。
错误信息
在项目被拆分为路由和控制器后,服务器出现以下错误:
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,
});
};
在 getWeather 前加上 export 后,服务器功能便恢复了。仅一个关键字,就解决了错误。
该项目目前涵盖的内容
在当前阶段,该天气服务已具备多项生产环境所需的要素:
- 用于代码仓库的 TypeScript 工具链
- 正在运行的 Express HTTP 进程
- 采用独立目录结构而非单个巨型文件
- 与处理函数相连的 URL 路由器
- 用于处理请求逻辑的处理模块
- 如
:city这样的路径参数 - 结构化的 JSON 数据载荷
- 通过本地检查路径和导出项来解决模块缺失的问题
理解路由参数
可以使用 Postman 等 HTTP 客户端来测试这些路由:
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>对于 GET /weather/mumbai,req.params.city会包含什么内容? 就是路径中输入的字符串"mumbai",保持原样不变。
后续内容
模拟天气功能已经完成其作用。下一层则是实时天气 API,具体包括:
- 调用外部 HTTP API
- 使用环境变量(
.env),避免密钥硬编码在源代码中 - 利用
fetch或axios发送请求 - 妥善处理超时和上游服务故障
- 设立服务文件夹,让控制器无需承担 HTTP 客户端相关功能
请求路径还增加了另一层结构:
Browser/Postman
│
▼
Routes
│
▼
Controllers
│
▼
Services
│
▼
Weather API (external)
这种分层结构与许多实际使用的 Express 应用类似。逐步构建可以确保在实时集成出现故障时能有清晰的回滚点,而不会将所有功能都塞进同一个文件中。