dmikester1
dmikester1

Reputation: 1362

How to dynamically create the models for Sequelize in Node 14 / Sequelize 6

I have a project that uses Node and Sequelize on the backend. It was working fine until I had to upgrade Node and Sequelize to the latest versions. I realize I could downgrade, but I have Node 14 being used in my other projects and don't want to figure out how to use multiple Node versions at the same time.

I have my index file in my models folder reading in all the model js files and generating the models automatically.

This is my code that does that:

'use strict';

import fs from 'fs';
import path from 'path';
import Sequelize from 'sequelize';
// import DataTypes from 'sequelize/lib/data-types';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
import { dirname } from 'path';
const __dirname = dirname(__filename);
const basename = path.basename(__filename);
import config from '../config/config.js';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const db = {};

const sequelize = new Sequelize(config);

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

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

The commented out line was what I was using before. But now Sequelize 6 removed the import option. So I was able to figure out a hack to get "require" working in that file. However the issue now is when I try and run my app, I get this error:

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: C:\....\models\Chemicals.js require() of ES modules is not supported. require() of C:\....\models\Chemicals.js from C:\....\models\index.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules. Instead rename Chemicals.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from C:\....\package.json.

I don't want to have to rename all my models to '.cjs' if I don't have to. And I don't want to remove "type":"module" either.

Upvotes: 1

Views: 1572

Answers (1)

mik rus
mik rus

Reputation: 139

The errors there are pretty clear as far as it goes,

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: C:\....\models\Chemicals.js 
require() of ES modules is not supported. 

require() of C:\....\models\Chemicals.js from C:\....\models\index.js is an
ES module file as it is a .js file whose nearest parent package.json contains 
"type": "module" which defines all .js files in that package scope as ES modules. 

Instead rename Chemicals.js to end in .cjs, change the requiring 
code to use import(), or remove "type": "module" from C:\....\package.json.

and the last line there has three suggestions:

  • rename Chemicals.js to end in .cjs,
  • change the requiring code to use import(),
  • remove "type": "module" from C:....\package.json.

A few caveats with these suggestions:

import() calls are asynchronous, so you need to account for that, not really a drop in replacement. (It seems curious to me that, if this is presumably some production level backend code, you are using synchronous calls for anything!). A quick refactor here might look like

'use strict';

import fs from 'fs';
import path from 'path';
import Sequelize from 'sequelize';
// import DataTypes from 'sequelize/lib/data-types';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
import { dirname } from 'path';
const __dirname = dirname(__filename);
const basename = path.basename(__filename);
import config from '../config/config.js';
// no need for these bits any more, always nice
// import { createRequire } from 'module';
// const require = createRequire(import.meta.url);
const db = {};

const sequelize = new Sequelize(config);
fs.readdir(__dirname, (err, files) => {
  if (err)
    console.log(err);
  else {
    const promise_arr = files
          .filter((file) => {
            return (
              file.indexof('.') !== 0 &&
                file !== basename &&
                file.slice(-3) === '.js'
            );
          })
          .map((file) => {
            // const model = sequelize['import'](path.join(__dirname, file));
            return import(path.join(__dirname, file))
          });
    Promise.all(promise_arr).then((models)=>{
      models.forEach((model)=>db[model.name] = model(sequelize,Sequelize.DataTypes)) 
      associateCB()
    })
  }
})

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

modifying your package json might have other ramifications if this is something that is published as an npm library, something to be required by something else. Doesnt seem like it so this one might be your best bet!


the .cjs seems fine too, but just cant help but feel it looks weird!


Why did this happen to you: Because updating to node 14 kicked into effect the es module system in node as a stable, operative feature. the package.json type setting is the actual cause here of the error. See here

Upvotes: 3

Related Questions