javascriptnode.jsecmascript-6sequelize.jsbabeljs

TypeError: Class constructor model cannot be invoked without 'new'


I'm using Sequelize together with Node and JavaScript in one app. As you know when you execute sequelize-init it creates config, migrations, models and seeders folder. Inside of the models folder, there is an index.js file (generated by the cli), which is responsible for reading all of the models and their associations from the current folder:

index.js

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const sequelize = require('../database/connection');
const db = {};

fs
  .readdirSync(__dirname)
  .filter(file => {
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
  })
  .forEach(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);
  }
});

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

module.exports = db;

When I run my app, an error appears: TypeError: Class constructor model cannot be invoked without 'new' at line 17 which is the statement: var model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); Reading some of the posts related to this problem I found out that I need to install the @babel/preset-env package along with @babel/cli, @babel/core, @babel/node. I also created a .babelrc file in the root directory, containing the following code:

.babelrc

{
    "presets": [
      [
        "@babel/preset-env",
        {
          "targets": {
            "esmodules": true
          }
        }
      ]
    ]
}

and updated my start option of the scripts tag inside package.json to: "nodemon --exec babel-node app.js" (I don't use webpack)

But when I run the app the error still appears. What do I miss or haven't set correctly?


Solution

  • Note: Check the Why section in the end! If you want to know.

    Problem

    The problem is ES6 classes => are not transpilled with there full features to ES5! Sequelize is relying on some features

    Or

    Using Sequelize.import in place of require! Don't! (Babel or any transpiler works with require (or es import) and how sequelize.import work is causing problem!)!

    Answer

    I guess you are using

    export class Course extends Model {} // <== ES6 classes
    
    Course.init({
    

    Your babel configuration is what's causing that!

    You can check that on this github thread comment

    This worked for the guy

    {
      "presets": [
        ["env", {
          "targets": {
            "node": "current"
          }
        }]
      ]
    }
    

    Or

    {
      "presets": [
        [
          "@babel/preset-env",
          {
            "targets": {
              "node": true
            }
          }
        ]
      ]
    }
    

    comment

    Making babel use ES6 classes!

    Note that if in the other hand you just stick with sequelize.define() way

    export const User = sequelize.define('User', {
      // Model attributes are defined here
      firstName: {
        type: DataTypes.STRING,
        allowNull: false
      },
    

    It will work just right in ES5!

    Don't use Sequelize.import to import Models

    If you are doing then don't! Use require or es import! The transpiler doesn't handle Sequelize.import directly! And it seems to cause problem

    Comment showing that

    For me it was because of ES6 with Typescript

    Typescript

    Default target was ES5!

    Change that to ES6+! And all start working!

    For example

    "target": "ES2021", 
    

    ES5 with sequelize.define works

    Know that ES5 with sequelize.define works just fine!

    enter image description here

    ES5 with Class extends doesn't work

    enter image description here

    Illustration

    enter image description here

    ES6+ works always

    enter image description here

    enter image description here

    Why! Babel doesn't normally transpile right!! ???

    You can see that in this github comment

    It turns out that an ES6-class transpiled by babel can't extend a real ES6 class (The Sequelize.Model ES6-class for example).
    So class MyEntity extends Sequelize.Model { ... } won't work if MyEntity is transpiled into ES5.

    And here the stackoverflow answer explaining better: https://stackoverflow.com/a/36657312/7668448

    Due to the way ES6 classes work, you cannot extend a native class with a transpiled class. If your platform supports native classes

    You can see more depth elements about the why and how possibly such a transpilation of ES6 Class to ES5 can be done to support full features and compatiblity! in the links bellow:

    https://github.com/microsoft/TypeScript/issues/17088

    https://github.com/microsoft/TypeScript/issues/15397

    And here an article (comparing-different-ways-of-transpiling-es6-class-to-es5) that go in more details about the subject