You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ESM+nodemon启动Node.js应用遇ERR_MODULE_NOT_FOUND报错求助

ESM结合nodemon运行Node.js应用时出现ERR_MODULE_NOT_FOUND错误

报错信息

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'app.js' imported from /Users/oumarmauret/Documents/AkalanSchool/models/index.js

models/index.js代码内容

import fs from 'fs';
import path from 'path';
import { Sequelize, DataTypes } from 'sequelize';

const __filename = path.basename(import.meta.url);
const __dirname = path.dirname(__filename);
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
import configJson from '../config/config.json' assert { type: 'json' };
const config = configJson[env];
const db = {};

let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(
    config.database,
    config.username,
    config.password,
    config
  );
}

fs.readdirSync(__dirname)
  .filter((file) => {
    return (
      file.indexOf('.') !== 0 &&
      file !== basename &&
      file.slice(-3) === '.js' &&
      file.indexOf('.test.js') === -1
    );
  })
  .forEach(async (file) => {
    const { default: model } = await import(path.join(__dirname, file));
    db[model.name] = model(sequelize, DataTypes);
  });

Object.keys(db).forEach((modelName) => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

export default db;

app.js代码内容

import express from 'express';
import bodyParser from 'body-parser';
import rateLimit from 'express-rate-limit';
import helmet from 'helmet';
import morgan from 'morgan';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import AppError from './utils/appError.js';
import globalErrorHandler from './controllers/errorController.js';
import path from 'path';
import { getCurrentUser } from './controllers/googleAuth.js';
import { getFacebookUserData } from './controllers/facebookAuth.js';
import session from 'express-session';
import xss from 'xss-clean';
import { Sequelize } from 'sequelize';
import config from './config/config.json' assert { type: 'json' };

const app = express();

// Import des routes
import adminRoutes from './routes/admin.routes.js';
import oauthRoutes from './routes/oauth.routes.js';
import categoriesRoutes from './routes/categories.routes.js'; // Ajout de cette ligne
import downloadRoutes from './routes/courseDownload.routes.js';
import coursesRoutes from './routes/course.routes.js';
import moduleRoutes from './routes/module.routes.js';
import LessonRoutes from './routes/lesson.routes.js';
import QuizRoutes from './routes/quiz.routes.js';
import QuestionRoutes from './routes/question.routes.js';
import AnswerRoutes from './routes/answer.route.js';
import userRoutes from './routes/user.routes.js';
import reviewRoutes from './routes/review.routes.js';
import statsRoutes from './routes/enrolment.routes.js';

app.enable('trust proxy');
app.set('views', path.join(import.meta.url, './views'));

// 1) GLOBAL MIDDLEWARES
// Set security HTTP headers
app.use(helmet());
app.use(
  cors({
    credentials: true,
  })
);

app.use(express.static(path.join(import.meta.url, 'views')));
app.use(express.static(path.join(import.meta.url, 'public')));
app.get('/', (req, res) => {
  res.render('signin.ejs');
});

// Limit requests from same API
const limiter = rateLimit({
  max: 5,
  windowMs: 60 * 60 * 1000,
  //one hour
  message:
    'Trop de demandes provenant de cette IP, veuillez réessayer dans une heure !',
});

app.use('/api/', limiter);

// Body parser, reading data from body into req.body
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));
app.use(cookieParser());

app.use(
  session({
    secret: 'Keep it secret',
    resave: false,
    saveUninitialized: false,
    cookie: { secure: false },
  })
);

app.get('/myaccount', getCurrentUser, async (req, res) => {
  try {
    const fullname = res.locals.user.fullname;
    const message = `Hello ${fullname} (Connected via Google)`;
    res.send(message);
  } catch (error) {
    console.error('Error fetching user data:', error.message);
    res.status(500).send(error.message);
  }
});

app.get('/myprofile', getFacebookUserData, async (req, res) => {
  try {
    const fullname = res.locals.user.fullname;
    const message = `Hello ${fullname} (Connected via Facebook)`;
    res.send(message);
  } catch (error) {
    console.error('Error fetching user data:', error.message);
    res.status(500).send(error.message);
  }
});

//Data sanitization against SQL query injection

//Data sanitization against XSS
app.use(xss());

// Initialize Sequelize
const sequelize = new Sequelize(
  config[process.env.NODE_ENV || 'development'].database,
  config[process.env.NODE_ENV || 'development'].username,
  config[process.env.NODE_ENV || 'development'].password,
  config[process.env.NODE_ENV || 'development']
);

// Check database connection
async function sequelizeDatabaseConnect() {
  try {
    await sequelize.authenticate();
    console.log('Connexion à la base de données réussie.');
  } catch (error) {
    console.error('Erreur de connexion à la base de données :', error);
  }
}

sequelizeDatabaseConnect();

if (process.env.NODE_ENV === 'development') {
  app.use(morgan('dev'));
}

app.use('/uploads', express.static(path.join(import.meta.url, 'uploads')));
app.use(cors());

app.use((req, res, next) => {
  req.requestTime = new Date().toISOString();
  next();
});

// Routes
app.use('/api/v1/courses', categoriesRoutes);
app.use('/api/v1/courses/downloads', downloadRoutes);
app.use('/api/v1/courses', coursesRoutes);
app.use('/api/v1/courses', LessonRoutes);
app.use('/api/v1/courses', moduleRoutes);
app.use('/api/v1/courses', QuizRoutes);
app.use('/api/v1/courses', QuestionRoutes);
app.use('/api/v1/courses', AnswerRoutes);
app.use('/api/v1/admins', adminRoutes);
app.use('/api/v1/auth', oauthRoutes);
app.use('/api/v1/users', userRoutes);
app.use('/api/v1/stats', statsRoutes);
app.use('/api/v1/reviews', reviewRoutes);

app.all('*', (req, res, next) => {
  next(
    new AppError(`Impossible de trouver ${req.originalUrl} sur ce serveur`, 404)
  );
});

app.use(globalErrorHandler);

export default app;

package.json配置内容

{
  "name": "akalanschool",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "start": "nodemon --experimental-modules server.js",
    "start:prod": "NODE_ENV=production nodemon server.js"
  },
  "keywords": [],
  "author": "Oumar MAURET",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.6.5",
    "bcrypt": "^5.1.1",
    "body-parser": "^1.20.2",
    "cookie-parser": "^1.4.6",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "ejs": "^3.1.9",
    "express": "^4.18.2",
    "express-rate-limit": "^7.1.5",
    "express-session": "^1.17.3",
    "firebase": "^10.8.0",
    "helmet": "^7.1.0",
    "html-to-text": "^9.0.5",
    "jsonwebtoken": "^9.0.2",
    "lodash": "^4.17.21",
    "morgan": "^1.10.0",
    "multer": "^1.4.5-lts.1",
    "mysql": "^2.18.1",
    "mysql2": "^3.6.5",
    "nanoid": "^5.0.5",
    "nodemailer": "^6.9.7",
    "nodemon": "^3.0.1",
    "querystring": "^0.2.1",
    "sequelize": "^6.35.0",
    "sharp": "^0.33.2",
    "xss-clean": "^0.1.4"
  }
}

问题描述

使用ECMAScript Modules(ESM)结合nodemon运行Node.js应用时出现上述报错,已尝试重启服务器、修改启动脚本为nodemon --experimental-modules server.js,但问题仍未解决,怀疑与nodemon适配ESM的配置有关,请求分析报错原因并提供解决方案。


报错原因分析

  1. __dirname计算错误:在models/index.js中,原代码通过path.basename(import.meta.url)获取__filename,再用path.dirname(__filename)得到__dirname,但import.meta.url是带file://前缀的URL,path.basename处理后仅得到文件名(如index.js),path.dirname对文件名处理会返回.(当前工作目录,即项目根目录)。这导致fs.readdirSync(__dirname)读取了项目根目录下的所有文件(包括app.js),后续尝试导入app.js时,因为路径没有./前缀,ESM会将其当作npm包查找,从而抛出ERR_MODULE_NOT_FOUND错误。

  2. 启动脚本冗余参数:Node.js 14.13.0及以上版本已默认支持ESM,--experimental-modules参数已无需使用,冗余参数不会导致报错,但属于无效配置。

解决方案

  1. 修正__dirname的正确获取方式:
    在models/index.js顶部替换原__filename和__dirname的计算代码,使用ESM标准的文件路径处理方式:

    import { fileURLToPath } from 'url';
    import path from 'path';
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = path.dirname(__filename);
    

    此方式会将import.meta.url转换为本地文件路径,确保__dirname指向models目录,fs.readdirSync只会读取该目录下的模型文件,不会误读根目录的app.js。

  2. 优化nodemon启动脚本:
    修改package.json中的scripts字段,移除冗余参数,生产环境无需使用nodemon:

    "scripts": {
      "start": "nodemon server.js",
      "start:prod": "NODE_ENV=production node server.js"
    }
    
  3. 验证server.js的导入路径:
    确保server.js中正确导入app.js,路径需带相对前缀:

    import app from './app.js';
    const port = process.env.PORT || 3000;
    
    app.listen(port, () => {
      console.log(`Server running on port ${port}`);
    });
    

内容的提问来源于stack exchange,提问作者Oumar MAURET

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 23:12:02