1. Express.js TypeScript 项目里 npm run dev 报 Cannot find module 的真实原因如果你正在用 Express.js 写后端并且把项目从纯 JavaScript 迁移到了 TypeScript那么大概率会遇到这个报错Error: Cannot find module entity/User。这个报错最迷惑的地方在于你的编辑器里明明能跳转、能提示、能自动补全TypeScript 编译也不报红但一执行npm run dev就直接崩掉。核心检索词就是 Express.js、ts、npm run dev、tsconfig-paths、module-alias这篇文章会把这条链路彻底讲清楚。先说结论这不是你的路径写错了而是「TypeScript 编译器认识的路径别名」和「Node.js 运行时认识的模块解析规则」根本是两套东西。tsconfig.json里的paths只服务于类型检查和编译阶段ts-node默认不会把它翻译成运行时的模块解析逻辑。所以你在.ts文件里写import { EntityUser } from entity/User编辑器满意了但 Node 在运行时拿着entity/User这个裸模块名去node_modules里找当然找不到。这个问题会分成两个阶段暴露出来。第一阶段是开发阶段用ts-node直接跑.ts源码时报错第二阶段是构建之后用pm2或node跑dist目录里的.js文件时又报一次因为编译产物里的require(entity/User)原封不动地保留了别名。很多人只解决了第一个阶段上线后又踩第二个坑。下面我会把两个阶段都拆开给出可复制的配置并且顺带演示一次最小验证dev 启动后别名解析生效同时通过 TaoToken 的统一 Key 和 API 通道调用模型接口也正常。2. 前置准备TaoToken 统一 Key 与 API 通道在动手改配置之前先把后面验证环节要用到的调用通道准备好。TaoToken 的作用是把多个模型服务的调用收敛到一个统一的 Key 和统一的 API 地址上你不用在项目里维护一堆不同厂商的 Key 和不同的 base URL。对于 Express.js 这种需要频繁在服务端发起模型请求的场景统一通道能省掉很多环境变量管理的麻烦。你需要拿到一个 API Key入口在控制台的 API Keys 页面https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。创建之后复制出来后面写进.env文件。API 的基础地址是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 base URL 使用。如果你更习惯先在一个对话界面里确认模型是否可用可以先用模型对话页面测一下https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。长期做编码或者 Agent 类任务的话Coding Plan 会更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到参数问题可以对照查。注意API Key 只放在服务端环境变量里不要写进任何会被提交到仓库的文件。Express.js 项目里建议用.env加dotenv并且把.env加进.gitignore。3. 可复制配置tsconfig.json 路径映射与 dev 启动脚本先看项目结构假设你的 Express.js 项目长这样portal/ ├── bin/ │ └── portal.ts ├── src/ │ ├── app.ts │ ├── entity/ │ │ └── User.ts │ ├── user/ │ │ └── invitation.ts │ └── utility/ │ └── index.ts ├── tsconfig.json └── package.jsontsconfig.json里关键的几项配置如下。baseUrl必须设置paths才会生效outDir决定编译产物落在哪里后面配module-alias要用到。{ compilerOptions: { target: ES2020, module: commonjs, moduleResolution: node, baseUrl: ., outDir: ./dist, rootDir: ., paths: { entity/*: [./src/entity/*], utility/*: [./src/utility/*] }, esModuleInterop: true, strict: true, skipLibCheck: true }, include: [src/**/*.ts, bin/**/*.ts] }这里有个容易忽略的点rootDir设成.之后编译产物会保留src这一层目录也就是dist/src/entity/User.js。如果你把rootDir设成./src产物就变成dist/entity/User.js那么后面module-alias的映射路径也要跟着改。两种都行关键是前后一致。开发阶段用ts-node跑需要装tsconfig-paths并让它在启动时加载yarn add -D ts-node tsconfig-paths typescript然后在package.json的 scripts 里这样写{ scripts: { dev: ts-node -r tsconfig-paths/register bin/portal.ts, build: tsc -p tsconfig.json, start: node dist/bin/portal.js } }-r tsconfig-paths/register是关键它在ts-node启动时把tsconfig.json里的paths注册进 Node 的模块解析流程这样运行时就能认识entity/User了。如果你用的是nodemon做热重载可以写成{ scripts: { dev: nodemon --exec \ts-node -r tsconfig-paths/register\ bin/portal.ts } }构建之后运行dist里的.js文件tsconfig-paths就不管用了因为那是编译期的东西。这时候要用module-aliasyarn add module-alias在package.json里加一段映射注意路径要指向编译产物{ _moduleAliases: { entity: dist/src/entity, utility: dist/src/utility } }然后在src/app.ts的最顶部注意是最顶部第一行就引入注册代码import module-alias/register; import express from express; // 其余 import 放在后面module-alias/register必须在任何使用别名的模块被加载之前执行否则映射还没生效require就已经失败了。这也是为什么很多人加了这行还是报错因为位置放错了。4. 验证请求dev 启动后别名解析与 TaoToken 调用都正常配置改完先跑一次开发模式yarn dev如果之前是Cannot find module entity/User现在应该能正常启动并监听端口。为了确认别名真的生效而不是碰巧没走到那行代码可以在src/user/invitation.ts里加一个显式引用import { EntityUser } from entity/User; export function checkAlias() { console.log(alias resolved:, typeof EntityUser); return EntityUser; }在bin/portal.ts里调用一次checkAlias()启动后控制台打印出alias resolved: function说明tsconfig-paths已经在运行时接管了解析。接着验证 TaoToken 通道。在项目里装axios和dotenvyarn add axios dotenv新建.envTAOTOKEN_API_KEY你的Key TAOTOKEN_BASE_URLhttps://taotoken.net/api写一个最小的调用函数放在src/utility/taotoken.tsimport axios from axios; const client axios.create({ baseURL: process.env.TAOTOKEN_BASE_URL, headers: { Authorization: Bearer ${process.env.TAOTOKEN_API_KEY}, Content-Type: application/json, }, timeout: 30000, }); export async function pingModel() { const res await client.post(/v1/chat/completions, { model: gpt-4o-mini, messages: [{ role: user, content: reply with ok }], max_tokens: 10, }); return res.data; }在bin/portal.ts里加载dotenv并调用import dotenv/config; import { pingModel } from utility/taotoken; pingModel() .then((data) console.log(taotoken ok:, JSON.stringify(data).slice(0, 120))) .catch((err) console.error(taotoken fail:, err.message));再次yarn dev如果控制台同时出现alias resolved: function和taotoken ok: {...}说明两件事都通了别名在 dev 模式下解析正常TaoToken 的统一 Key 和 API 通道调用正常。这一步很关键因为很多人的别名问题解决了但环境变量没加载模型调用又失败误以为是别名配置把请求搞坏了。构建之后再验证一次yarn build yarn startdist目录下运行同样应该打印出成功信息。如果这里报Cannot find module entity/User回到第 3 节检查_moduleAliases的路径是否和outDir一致。5. 本篇常见错排查第一个高频错误是tsconfig-paths装了但没在启动命令里注册。只装包不写-r tsconfig-paths/register等于没装。检查package.json的dev脚本确认-r参数存在。第二个是baseUrl缺失。paths必须配合baseUrl才有意义如果tsconfig.json里只有paths没有baseUrlTypeScript 自己都会警告运行时更不可能解析。第三个是module-alias/register引入位置太靠后。它必须是入口文件的第一条 import任何在它之前执行的模块如果用了别名都会失败。把import module-alias/register提到最顶部。第四个是_moduleAliases路径写错。outDir是./dist、rootDir是.时产物在dist/src/entity如果rootDir是./src产物在dist/entity。映射写错就会在构建后报模块找不到。第五个是ts-node版本和moduleResolution不匹配。moduleResolution设成node是最稳的设成bundler或node16时tsconfig-paths的行为可能不同。Express.js 服务端项目建议保持node。第六个是环境变量没加载导致模型调用失败被误判成别名问题。确认dotenv/config在入口最早执行或者用node -r dotenv/config启动。TaoToken 的 Key 如果没读到请求会返回鉴权错误而不是模块找不到两者要区分开。提示排查时把NODE_OPTIONS里的调试打开node --trace-require或者ts-node的--log-error能看到真实的解析路径比猜快得多。6. 语义一致的收尾与入口分流把这条链路理顺之后Express.js TypeScript 项目的模块解析就不再是玄学开发阶段靠tsconfig-paths在运行时注册paths构建之后靠module-alias在产物里做映射两者各管一段缺一不可。TaoToken 在这里扮演的是统一调用通道的角色让服务端的模型请求不用散落在各个厂商的 Key 和地址里。如果你在接入过程中遇到鉴权或参数问题先去 API Keys 页面确认 Key 状态https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 再对照接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。想先验证模型是否可用用模型对话页面最快https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。长期做编码或 Agent 任务Coding Plan 的入口在https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。官网首页https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。