
本文详解如何在 Node.js(ESM 环境)中正确导出和使用多个 Express 路由,避免 Route.get() requires a callback function but got a [object Undefined] 错误,强调命名导出、Router 实例导出及模块引用规范。
本文详解如何在 node.js(esm 环境)中正确导出和使用多个 express 路由,避免 `route.get() requires a callback function but got a [object undefined]` 错误,强调命名导出、router 实例导出及模块引用规范。
在 Node.js 项目中启用 ES 模块(通过 type: "module" 或 .mjs 后缀)后,Express 应用的模块组织方式需严格遵循 ESM 规范——尤其是导出(export)与导入(import)的语义一致性。常见错误(如 Route.get() requires a callback function but got a [object Undefined])往往源于对 ESM 导出机制的误解:将单个路由处理函数(如 router.get(...) 返回值)误作可导出路由器对象导出,或在控制器中混用默认导出与命名导出造成引用失效。
核心原则有三点:
✅ 控制器文件只使用命名导出(export const fn = ...),禁用 export default —— 因为一个控制器通常提供多个逻辑独立的处理函数,命名导出更清晰、可预测且支持按需导入;
✅ 路由文件导出 express.Router() 实例本身(export default router),而非某次 .get() 调用的返回值(该返回值是 router 实例,但语义上不应被单独导出);
✅ 入口文件(如 app.js)通过默认导入接收完整路由器实例,并直接挂载到应用中间件链。
以下是推荐的结构化实现:
1. 控制器文件(controllers/shop.js)——仅命名导出,无默认导出
export const getIndex = (req, res, next) => {
res.render('shop/index', {
pageTitle: 'Shop',
path: '/',
});
};
export const getProducts = (req, res, next) => {
res.render('shop/product', {
pageTitle: 'Products',
path: '/product',
});
};
// ✅ 正确:多个功能函数均以命名方式导出
// ❌ 避免:export default getIndex; (会覆盖命名导出,导致其他函数不可见)2. 路由文件(routes/shop.js)——配置并导出路由器实例
import express from 'express';
// 推荐显式导入(更易维护、支持 tree-shaking):
import { getIndex, getProducts } from '../controllers/shop.js';
const router = express.Router();
// 所有路由注册均作用于同一 router 实例
router.get('/', getIndex);
router.get('/product', getProducts);
router.get('/cart', (req, res) => res.render('shop/cart')); // 也可内联,但建议抽离至 controller
// ✅ 关键:导出配置完成的 router 实例
export default router;3. 入口文件(app.js)——导入并挂载路由器
import express from 'express';
import shopRouter from './routes/shop.js'; // 默认导入 router 实例
const app = express();
// 直接挂载,路径前缀自动继承(如 /shop 下所有子路由)
app.use('/shop', shopRouter);
// 或挂载至根路径(等同原示例)
// app.use(shopRouter);⚠️ 重要注意事项:
- 不要导出 router.get(...) 的返回值:router.get() 返回 router 实例用于链式调用,但它不是“路由处理器”,而是配置器。导出它会导致导入时得到一个 Router 对象,而非可执行的中间件函数——这正是报错 got a [object Undefined] 的根本原因(实际是 undefined,因未正确导出)。
- *避免 `import as xxx在生产环境**:虽然语法合法,但会阻碍静态分析与打包优化;明确列出所需函数(如import { getIndex }`)更健壮、可读性更高。
- 确保文件扩展名与 package.json 配置一致:若使用 .js 文件,须在 package.json 中声明 "type": "module";若用 .mjs,则无需该字段,但需统一扩展名。
- 路径别名与解析:ESM 中相对路径必须带扩展名(如 './routes/shop.js'),Node.js 不再自动补全 .js,遗漏将导致 ERR_MODULE_NOT_FOUND。
总结而言,ES 模块下的 Express 路由设计应以「路由器实例为中心」:控制器专注定义处理函数(命名导出),路由文件专注组合这些函数到 Router 实例并导出,应用层专注挂载。这种分层清晰、职责单一的模式,不仅解决多路由问题,更为后续测试、拆分与维护奠定坚实基础。











