13

Sequelize docs claim that it works with Typescript but in order for it to be useful in production one needs to use DB migration scripts. These can only be executed using the Sequelize CLI but this CLI seems to have no regard for Typescript at all. It only generates (and apparently runs) JS files. There is a "sequelize-cli-typescript" project on NPM but it's 4 years old! And the only answer on Stack Overflow to a related question is literally 6 words long, none of them useful: Using sequelize cli with typescript

My setup is like so:

I created a basic Node app with Typescript and a simple tsconfig.js like so:

{
  "compilerOptions": {
    "allowJs": true,
    "module": "commonjs",
    "esModuleInterop": true,
    "target": "es6",
    "noImplicitAny": true,
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "dist",
    "baseUrl": ".",
    "paths": {
      "*": [
        "node_modules/*"
      ]
    }
  },
  "include": [
    "*"
  ]
}

I'm also using ESLint with the following .eslintrc:

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint"
  ],
  "extends": [
    "airbnb-base", "airbnb-typescript/base"
  ],
  "parserOptions": {
    "project": "./tsconfig.json"
  }
}

I then tried running the Sequelize CLI init command, npx sequelize-cli init as described in the DB migrations documentation. This caused a file models/index.js to be created with the following content:

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[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');
  })
  .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;

This causes my IDE (VS Code) to show an error:

Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser. The file does not match your project config: models/index.js. The file must be included in at least one of the projects provided.

I have no idea what this is supposed to mean. I've read numerous explanations and none makes sense to me. I have allowJs set to true in tsconfig and include all. What am I missing? More importantly how can I use Typescript for DB migrations and CLI generated code?

JeneralJames
  • 314
  • 1
  • 3
  • 10

4 Answers4

13

As for generated code for connecting to DB and registering models you should convert it manually to ts (a one-time thing usually).
As for migrations I use the following workaround:

  1. configure migrations path in .sequelizerc to a subfolder in the folder where the compiled app will be placed by TS (it's important in order to be able to apply migrations on testing and prod environments), something like:
const { resolve } = require('path');

module.exports = {
    config: resolve('build/application/config.js'),
    'seeders-path': resolve('build/application/seeders'),
    'migrations-path': resolve('build/application/migrations'),
    'models-path': resolve('application/models')
};
  1. generate a migration file with a given name:
sequelize migration:create --name add-some-table
  1. move the generated file to application/migrations and change its extension to .ts
  2. replace the content of the file with some kind of a migration Typescript-template:
import { QueryInterface, DataTypes, QueryTypes } from 'sequelize';

module.exports = {
    up: (queryInterface: QueryInterface): Promise<void> => queryInterface.sequelize.transaction(
        async (transaction) => {
          // here go all migration changes
        }
    ),

    down: (queryInterface: QueryInterface): Promise<void> => queryInterface.sequelize.transaction(
        async (transaction) => {
          // here go all migration undo changes
        }
    )
};
  1. add necessary changes to the migration file

Basically, it's possible to write some script file to simply generate .ts migration file copying a predefined template file in application/migrations folder directly with a given name to combine steps 2-4 into a single step.

If you will come up with a better solution eventually feel free to share it in the comments or even as a separate answer here.

Anatoly
  • 20,799
  • 3
  • 28
  • 42
  • 1
    I am getting an error "Dialect needs to be explicitly supplied as of v4.0.0." That happens only when I am trying to run migrations. For example with Model.sync() it actually works and creates the table so the problem is not with the config. It says "Loaded configuration file "dist\db\config.js"." before the error. Do you have any ideas? It only happens when using typescript – Giannis Savvidis Apr 10 '22 at 21:06
  • 1
    It seems Sequelize CLI doesn't see its config file where you should indicate connection settings – Anatoly Apr 11 '22 at 16:44
  • I am getting this error ERROR: Cannot find "/Users/vr/Desktop/code/ch/api/src/config/index.js". Have you run "sequelize init"? it seems that the sequelize cli is not reading the typescript config file, how do you fix that – PirateApp Oct 28 '22 at 05:11
  • this would be my question with erors if you dont mind helping out https://stackoverflow.com/questions/74231128/error-running-sequelize-seeder-on-typescript-based-setup – PirateApp Oct 28 '22 at 06:57
0

I faced configuration file issue when changed it to .ts I created .sequelizerc file and added config.ts filename to accept ts file configuration. This is working when we execute sequelize.sync() method. But while executing Sequlize migrations it doesn't work with .TS config file. When I changed it to .JS it started working.

0

If you're willing to settle with having only intellisense, TypeScript can help you with import types. You can add the type annotations to up and down method as follows:

// 00000000000000-your-migration.js
module.exports = {
    /**
     * @param {import('sequelize').QueryInterface} queryInterface
     * @param {import('sequelize').Sequelize} Sequelize
     * @returns {Promise<any>}
    */
    up: (queryInterface, Sequelize) => {
        /** migration */
    },
    /**
     * @param {import('sequelize').QueryInterface} queryInterface
     * @param {import('sequelize').Sequelize} Sequelize
     * @returns {Promise<any>}
    */
    down: (queryInterface, Sequelize) => {
        /** undo migration */
    },
};

And now in VSCode you should have: VSCode intellisense example snippet 1

VSCode intellisense example snippet 2

xab
  • 636
  • 7
  • 11
0

@Anatoly 's answer was tweaked because I was getting this error "ERROR: Dialect needs to be explicitly supplied as of v4.0.0".

I could have commented there but I'm currently not able to and someone might need this;

CAUSE OF PROBLEM The issue is that is because of typescript's transformation/compilation.

SOLUTION After inspecting the transpiled version of the config.ts file, I noticed that the config was exported as:

module.exports.default = config
But sequelize is expecting the config to be at module.exports.

So, I modified the config.ts file by appending this one line to the end of the file, and that resolved it for me.


module.exports = config;

tsconfig.json file

 "exclude": ["node_modules", "__tests__"],
 "compilerOptions": {
   "target": "ES2021",
   "module": "commonjs",
   "lib": ["es6"],
   "moduleResolution": "Node",
   "types": ["node", "jest"],
   // "sourceMap": true,
   "outDir": "./dist",
   "rootDir": "./",
   "allowSyntheticDefaultImports": true,
   "resolveJsonModule": true,
   "strict": true,
   "typeRoots": ["./types", "./digitalniweb-types"],
   "strictNullChecks": true,
   "esModuleInterop": true,
   "skipLibCheck": true,
    // "noImplicitAny": false
   // "strictPropertyInitialization": false
   // "strictNullChecks": false,
 }
}

config.ts

import dotenv from "dotenv";
import { Options } from "sequelize";

dotenv.config();

interface ConfigTs {
    development: Options;
    test: Options;
    production: Options;
}

const configDB: ConfigTs = {
    development: {
        username: process.env.DB_USER,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_NAME,
        host: process.env.DB_HOST,
        dialect: "postgres",
        dialectOptions: {
            charset: "utf8",
        },
        define: {
            timestamps: false,
        },
    },
    test: {
        username: process.env.DB_USER,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_NAME,
        host: process.env.DB_HOST,
        dialect: "postgres",
        dialectOptions: {
            charset: "utf8",
        },
        define: {
            timestamps: false,
        },
    },
    production: {
        username: process.env.DB_USER,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_NAME,
        host: process.env.DB_HOST,
        dialect: "postgres",
        dialectOptions: {
            charset: "utf8",
            multipleStatements: true,
        },
        logging: false,
        define: {
            timestamps: false,
        },
    },
};
export default configDB;

module.exports = configDB;


  1. configured migrations path in .sequelizerc to a subfolder in the folder where the compiled app will be placed by TS (it's important in order to be able to apply migrations on testing and prod environments), something like:
    const path = require("path")
    
    
    module.exports ={
        env: process.env.NODE_ENV || 'development',
        config: path.resolve("dist/config", "config.js"),
        "models-path": path.resolve("dist", 'models'),
        "seeders-path": path.resolve("dist", 'seeders'),
        "migrations-path": path.resolve("dist", 'migrations'),
    
    }
  1. generate a migration file with a given name:

    sequelize migration:create --name add-some-table

  2. move the generated file to Typescript migrations and change its extension to .ts

  3. replace the content of the file with some kind of a migration Typescript-template:


      import { QueryInterface, DataTypes, QueryTypes } from 'sequelize';
    
    module.exports = {
        up: (queryInterface: QueryInterface): Promise<void> => queryInterface.sequelize.transaction(
            async (transaction) => {
              // here go all migration changes
            }
        ),
    
        down: (queryInterface: QueryInterface): Promise<void> => queryInterface.sequelize.transaction(
            async (transaction) => {
              // here go all migration undo changes
            }
        )
    };
  1. add necessary changes to the migration file
  2. build your application back
Brisstone
  • 31
  • 1
  • 2
  • 6