Updating Sequelize Major Version (Or Not)

Daily Standup

Working on GFT after a work-trip break. Before diving in too deep I decided to upgrade Sequelize from version 4 to 5 on the project. There were some breaking changes which I became acquainted with šŸ˜ In the end I decided to delay the upgrade and instead focus on making progress in the app itself.

Default Column Generation

The immediately apparent change was how column names are auto-generated now, depending on whether underscored: true is defined on models. It used to be that if you set underscored to true, the column names are generated in an underscored form (this applies to default columns like created_at, updated_at, etc.). In Sequelize 5 theyā€™ve changed this to make auto-generated column names go in in camelCase with the field set to the underscored name. The behavior is defined here.

Apparently this is normal behavior on other ORMs, and I can see how it makes sense by reading some of the comments in the discussion. But my models werenā€™t set up in this way, and the result is that the model queries are changed significantly:

# SEQUELIZE 4
Executing (ff6153d8-1fd7-48cd-a7d2-254449eb2012): SELECT "user_id", "auth0_id", "status", "created_at", "updated_at", "deleted_at" FROM "LocalUsers" AS "LocalUser" WHERE (("LocalUser"."deleted_at" > '2019-04-13 08:15:47.032 +00:00' OR "LocalUser"."deleted_at" IS NULL) AND "LocalUser"."auth0_id" = 'auth0|hash');

# SEQUELIZE 5
Executing (18d55c6c-a112-4b1e-a17f-de6a10390a3f): SELECT "user_id", "auth0_id", "status", "created_at", "updated_at", "deleted_at", "created_at" AS "createdAt", "updated_at" AS "updatedAt", "deleted_at" AS "deletedAt" FROM "local_users" AS "LocalUser" WHERE ("LocalUser"."deleted_at" IS NULL AND "LocalUser"."auth0_id" = 'auth0|hash');

This prompted some NOT NULL violation errors, since the query was looking for fields like createdAt which donā€™t exist in my models. The solution is to define these auto-generated fields on every model:

const LocalUser = sequelize.define('LocalUser', { ... },
{
underscored: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
deletedAt: 'deleted_at',
}
);

This got the queries back to what I expected:

# SEQUELIZE 5 WITH createdAt updatedAt fields defined
Executing (92e27929-0895-4298-a050-790dae9d673d): SELECT "user_id", "auth0_id", "status", "created_at", "updated_at", "deleted_at", "deleted_at" AS "deletedAt" FROM "local_users" AS "LocalUser" WHERE ("LocalUser"."deleted_at" IS NULL AND "LocalUser"."auth0_id" = 'auth0|hash');

# SEQUELIZE 5 WITH createdAt updatedAt deletedAt fields defined
Executing (99656f16-6bcd-411d-82f1-42b2c28541ec): SELECT "user_id", "auth0_id", "status", "created_at", "updated_at", "deleted_at" FROM "local_users" AS "LocalUser" WHERE ("LocalUser"."deleted_at" IS NULL AND "LocalUser"."auth0_id" = 'auth0|hash');

So then I would need to add this to all my model definitions and migration files.

An alternative way forward could have been accept the camelCase auto-defined column names, and maybe there would be less changing to do, and it would make my model files less verbose. Though not sure how this would affect other areas of the model definitions without looking into this further.

Association Queries

The new version also changed how model associations are queried:

# SEQUELIZE 4
Executing (default): SELECT "Venue"."venue_id", "Venue"."venue_name", "Venue"."country_id", "Venue"."created_at", "Venue"."updated_at", "Country"."country_id" AS "Country.country_id", "Country"."country_name" AS "Country.country_name" FROM "Venues" AS "Venue" LEFT OUTER JOIN "Countries" AS "Country" ON "Venue"."country_id" = "Country"."country_id" ORDER BY "Venue"."venue_name";

# SEQUELIZE 5
Executing (default): SELECT "Venue"."venue_id", "Venue"."venue_name", "Venue"."country_id", "Venue"."created_at" AS "createdAt", "Venue"."updated_at" AS "updatedAt", "Country"."country_id" AS "Country.country_id", "Country"."country_name" AS "Country.country_name" FROM "venues" AS "Venue" LEFT OUTER JOIN "countries" AS "Country" ON "Venue"."country_id" = "Country"."country_id" ORDER BY "Venue"."venue_name";

ā€¦prompting the following error in Sequelize 5:

Unhandled rejection SequelizeDatabaseError: column Venue.country_id does not exist

I did not get into this fix.

Model Definitions

There was another big change, in how models are defined:

// SEQUELIZE 4
const Project = sequelize.define('Project', {
title: Sequelize.STRING,
description: Sequelize.TEXT
});

// SEQUELIZE 5
class Project extends Model { };
Project.init({
title: Sequelize.STRING,
description: Sequelize.TEXT
},
{
sequelize,
modelName: 'project',
}
);

I havenā€™t looked deeply into this Model class to understand this change, and decided that doing so (now) and re-doing all of the models in the new syntax could potentially take a looooot of time, time Iā€™d really rather spend building out a working prototype of the app with all of the features I want in a proof-of-concept. So ultimately I decided to revert back to Sequelize 4 and save this upgrade for another day.

Define Model Attributes on Global Level

Not related to the version change, but I found a code snippet to define model attributes on a global level, instead of in each model as I have been doing up to now:

module.exports = {
development: {
host: process.env.POSTGRES_HOST,
port: process.env.POSTGRES_PORT,
username: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
database: process.env.POSTGRES_DB,

dialect: 'postgres',
operatorsAliases: Op,

define: {
freezeTableName: true,
underscored: true,
},
},
};

I think I will implement this when I do do the upgrade.

Other Stuff

When the time does come to upgrade, hereā€™s a resource that may prove helpfulā€¦it describes how to use classes for model definition, before this was made the m.o. in Sequelize 5.

Up Next

Working on the model that I couldnā€™t get to work before the migrations detourā€¦wish me luck!