首页 / 文章 / 无法找到模块:调试 Express 导入及路由参数

无法找到模块:调试 Express 导入及路由参数

由于缺少控制器导入,TypeScript Express天气API无法正常运行。请检查路径、大小写及导出设置,并在调用实时天气API之前验证城市名称是否正确。

852 词

调试 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 应用类似。逐步构建可以确保在实时集成出现故障时能有清晰的回滚点,而不会将所有功能都塞进同一个文件中。