Notes

Installing and Using Sequelize

What is an ORM?

How to Initialize Sequelize

{
  "development": {
    "username": "catsdbuser",
    "password": "catsdbpassword",
    "database": "catsdb",
    "host": "127.0.0.1",
    "dialect": "postgres"
  }
}

Verifying that Sequelize can connect to the Database

// ./index.js

const { sequelize } = require("./models");

async function main() {
  try {
    await sequelize.authenticate();
  } catch (e) {
    console.log("Database connection failure.");
    console.log(e);
    return;
  }

  console.log("Database connection success!");
  console.log("Sequelize is ready to use!");

  // Close database connection when done with it.
  await sequelize.close();
}

main();

// Prints:
//
// Executing (default): SELECT 1+1 AS result
// Database connection success!
// Sequelize is ready to use!

Using Sequelize to Generate the Model File

npx sequelize model:generate --name Cat --attributes "firstName:string,specialSkill:string"

Examining (And Modifying) A Sequelize Model File

// ./models/cat.js

"use strict";
module.exports = (sequelize, DataTypes) => {
  const Cat = sequelize.define(
    "Cat",
    {
      firstName: DataTypes.STRING,
      specialSkill: DataTypes.STRING,
    },
    {}
  );
  Cat.associate = function (models) {
    // associations can be defined here
  };
  return Cat;
};

Using The Cat Model To Fetch And Update SQL Data

const { sequelize, Cat } = require("./models");

async function main() {
  try {
    await sequelize.authenticate();
  } catch (e) {
    console.log("Database connection failure.");
    console.log(e);
    return;
  }

  console.log("Database connection success!");
  console.log("Sequelize is ready to use!");

  const cat = await Cat.findByPk(1);
  console.log(cat.toJSON());

  await sequelize.close();
}

main();

// This code prints:
//
// Executing (default): SELECT "id", "firstName", "specialSkill", "age", "createdAt", "updatedAt" FROM "Cats" AS "Cat" WHERE "Cat"."id" = 1;
// {
//   id: 1,
//   firstName: 'Markov',
//   specialSkill: 'sleeping',
//   age: 5,
//   createdAt: 2020-02-03T21:32:28.960Z,
//   updatedAt: 2020-02-03T21:32:28.960Z
// }

Reading And Changing Record Attributes

async function main() {
  // Sequelize authentication code from above...

  const cat = await Cat.findByPk(1);
  console.log(`${cat.firstName} has been assigned id #${cat.id}.`);
  console.log(`They are ${cat.age} years old.`);
  console.log(`Their special skill is ${cat.specialSkill}.`);

  await sequelize.close();
}

main();

// This code prints:
//
// Executing (default): SELECT "id", "firstName", "specialSkill", "age", "createdAt", "updatedAt" FROM "Cats" AS "Cat" WHERE "Cat"."id" = 1;
// Markov has been assigned id #1.
// They are 5 years old.
// Their special skill is sleeping.
async function main() {
  // Sequelize authentication code from above...

  // Fetch existing cat from database.
  const cat = await Cat.findByPk(1);
  // Change cat's attributes.
  cat.firstName = "Curie";
  cat.specialSkill = "jumping";
  cat.age = 123;

  // Save the new name to the database.
  await cat.save();

  await sequelize.close();
}

// Prints:
//
// Executing (default): SELECT "id", "firstName", "specialSkill", "age", "createdAt", "updatedAt" FROM "Cats" AS "Cat" WHERE "Cat"."id" = 1;
// Executing (default): UPDATE "Cats" SET "firstName"=$1,"specialSkill"=$2,"age"=$3,"updatedAt"=$4 WHERE "id" = $5

main();

Database Migrations

Using Database Migrations

Sequelize Migration Files

// ./migrations/20200203211508-create-cat.js

"use strict";
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable("Cats", {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER,
      },
      firstName: {
        type: Sequelize.STRING,
      },
      specialSkill: {
        type: Sequelize.STRING,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable("Cats");
  },
};

Running A Migration

catsdb=> \d "Cats";
                                         Table "public.Cats"
    Column    |           Type           | Collation | Nullable
--------------+--------------------------+-----------+----------
 id           | integer                  |           | not null
 firstName    | character varying(255)   |           |
 specialSkill | character varying(255)   |           |
 createdAt    | timestamp with time zone |           | not null
 updatedAt    | timestamp with time zone |           | not null
Indexes:
    "Cats_pkey" PRIMARY KEY, btree (id)

Rolling Back A Migration

Writing A down Method

module.exports = {
  // ...
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable("Cats");
  },
};

// OR, async/await way:
module.exports = {
  // ...
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable("Cats");
  },
};

Advantages Of Migrations


CRUD Operations with Sequelize

Creating A New Record

const { sequelize, Cat } = require("./models");

async function main() {
  // Constructs an instance of the JavaScript `Cat` class. **Does not
  // save anything to the database yet**. Attributes are passed in as a
  // POJO.
  const cat = Cat.build({
    firstName: "Markov",
    specialSkill: "sleeping",
    age: 5,
  });

  // This actually creates a new `Cats` record in the database. We must
  // wait for this asynchronous operation to succeed.
  await cat.save();

  console.log(cat.toJSON());

  await sequelize.close();
}

main();
const { sequelize, Cat } = require("./models");

async function main() {
  const cat = await Cat.create({
    firstName: "Curie",
    specialSkill: "jumping",
    age: 4,
  });

  console.log(cat.toJSON());

  await sequelize.close();
}

main();

Reading A Record By Primary Key

const { sequelize, Cat } = require("./models");

async function main() {
  // Fetch the cat with id #1.
  const cat = await Cat.findByPk(1);
  console.log(cat.toJSON());

  await sequelize.close();
}

main();

Updating A Record

const { sequelize, Cat } = require("./models");

async function main() {
  const cat = await Cat.findByPk(1);

  console.log("Old Markov: ");
  console.log(cat.toJSON());

  // The Cat object is modified, but the corresponding record in the
  // database is *not* yet changed at all.
  cat.specialSkill = "super deep sleeping";
  // Only by calling `save` will the data be saved.
  await cat.save();

  console.log("New Markov: ");
  console.log(cat.toJSON());

  await sequelize.close();
}

main();

Destroying A Record

const process = require("process");

const { sequelize, Cat } = require("./models");

async function main() {
  const cat = await Cat.findByPk(1);
  // Remove the Markov record.
  await cat.destroy();

  await sequelize.close();
}

main();
async function main() {
  // Destroy the Cat record with id #3.
  await Cat.destroy({ where: { id: 3 } });

  await sequelize.close();
}

main();

Querying Using Sequelize

Basic Usage Of findAll To Retrieve Multiple Records

const { sequelize, Cat } = require("./models");

async function main() {
  // `findAll` asks to retrieve _ALL_ THE CATS!!  An array of `Cat`
  // objects will be returned.
  const cats = await Cat.findAll();

  // Log the fetched cats.
  console.log(JSON.stringify(cats, null, 2));

  await sequelize.close();
}

main();
const { sequelize, Cat } = require("./models");

async function main() {
  // Fetch all cats named Markov.
  const cats = await Cat.findAll({
    where: {
      firstName: "Markov",
    },
  });
  console.log(JSON.stringify(cats, null, 2));

  await sequelize.close();
}

main();
const { sequelize, Cat } = require("./models");

async function main() {
  // Fetch all cats named either Markov or Curie.
  const cats = await Cat.findAll({
    where: {
      firstName: ["Markov", "Curie"],
    },
  });
  console.log(JSON.stringify(cats, null, 2));

  await sequelize.close();
}

main();

Using findAll To Find Objects Not Matching A Criterion

const { Op } = require("sequelize");
const { sequelize, Cat } = require("./db/models");

async function main() {
  const cats = await Cat.findAll({
    where: {
      firstName: {
        // Op.ne means the "not equal" operator.
        [Op.ne]: "Markov",
      },
    },
  });
  console.log(JSON.stringify(cats, null, 2));

  await sequelize.close();
}

main();

Combining Criteria with Op.and

const { Op } = require("sequelize");
const { sequelize, Cat } = require("./models");

async function main() {
  // fetch cats with name != Markov AND age = 4.
  const cats = await Cat.findAll({
    where: {
      firstName: {
        [Op.ne]: "Markov",
      },
      age: 4,
    },
  });
  console.log(JSON.stringify(cats, null, 2));

  await sequelize.close();
}

main();
const { Op } = require("sequelize");
const { sequelize, Cat } = require("./models");

async function main() {
  const cats = await db.Cat.findAll({
    where: {
      [Op.and]: [{ firstName: { [Op.ne]: "Markov" } }, { age: 4 }],
    },
  });
  console.log(JSON.stringify(cats, null, 2));

  await sequelize.close();
}

main();

Combining Criteria with Op.or

const { Op } = require("sequelize");
const { sequelize, Cat } = require("./models");

async function main() {
  // fetch cats with name == Markov OR age = 4.
  const cats = await Cat.findAll({
    where: {
      [Op.or]: [{ firstName: "Markov" }, { age: 4 }],
    },
  });
  console.log(JSON.stringify(cats, null, 2));

  await sequelize.close();
}

main();

Querying With Comparisons

const { Op } = require("sequelize");
const { sequelize, Cat } = require("./models");

async function main() {
  // Fetch all cats whose age is > 4.
  const cats = await Cat.findAll({
    where: {
      age: { [Op.gt]: 4 },
    },
  });
  console.log(JSON.stringify(cats, null, 2));

  await sequelize.close();
}

main();

Ordering Results

const { sequelize, Cat } db = require("./models");

async function main() {
  const cats = await Cat.findAll({
    order: [["age", "DESC"]],
  });
  console.log(JSON.stringify(cats, null, 2));

  await sequelize.close();
}

main();

Limiting Results and findOne

const { sequelize, Cat } = require("./models");

async function main() {
  const cats = await Cat.findAll({
    order: [["age", "DESC"]],
    limit: 1,
  });
  console.log(JSON.stringify(cats, null, 2));

  await sequelize.close();
}

main();
const { sequelize, Cat } = require("./models");

async function main() {
  const cat = await Cat.findOne({
    order: [["age", "DESC"]],
  });
  console.log(JSON.stringify(cat, null, 2));

  await sequelize.close();
}

main();

Validations With Sequelize

Validating That An Attribute Is Not NULL

// ./models/cat.js
"use strict";
module.exports = (sequelize, DataTypes) => {
  const Cat = sequelize.define(
    "Cat",
    {
      firstName: DataTypes.STRING,
      specialSkill: DataTypes.STRING,
      age: DataTypes.INTEGER,
    },
    {}
  );
  Cat.associate = function (models) {
    // associations can be defined here
  };
  return Cat;
};
// ./models/cat.js
"use strict";
module.exports = (sequelize, DataTypes) => {
  const Cat = sequelize.define(
    "Cat",
    {
      firstName: {
        type: DataTypes.STRING,
        allowNull: false,
        validate: {
          notNull: {
            msg: "firstName must not be null",
          },
        },
      },
      specialSkill: {
        type: DataTypes.STRING,
        allowNull: false,
        validate: {
          notNull: {
            msg: "specialSkill must not be null",
          },
        },
      },
      age: {
        type: DataTypes.INTEGER,
        allowNull: false,
        validate: {
          notNull: {
            msg: "age must not be null",
          },
        },
      },
    },
    {}
  );
  Cat.associate = function (models) {
    // associations can be defined here
  };
  return Cat;
};

The notEmpty Validation

// ./models/cat.js
"use strict";
module.exports = (sequelize, DataTypes) => {
  const Cat = sequelize.define(
    "Cat",
    {
      firstName: {
        type: DataTypes.STRING,
        allowNull: false,
        validate: {
          notNull: {
            msg: "firstName must not be null",
          },
          notEmpty: {
            msg: "firstName must not be empty",
          },
        },
      },
      specialSkill: {
        type: DataTypes.STRING,
        allowNull: false,
        validate: {
          notNull: {
            msg: "specialSkill must not be null",
          },
          notEmpty: {
            msg: "specialSkill must not be empty",
          },
        },
      },
      // ...
    },
    {}
  );
  Cat.associate = function (models) {
    // associations can be defined here
  };
  return Cat;
};

Forbidding Long String Values

// ./models/cat.js
"use strict";
module.exports = (sequelize, DataTypes) => {
  const Cat = sequelize.define(
    "Cat",
    {
      firstName: {
        type: DataTypes.STRING,
        allowNull: false,
        validate: {
          notNull: {
            msg: "firstName must not be null",
          },
          notEmpty: {
            msg: "firstName must not be empty",
          },
          len: {
            args: [0, 8],
            msg: "firstName must not be more than eight letters long",
          },
        },
      },
      // ...
    },
    {}
  );
  Cat.associate = function (models) {
    // associations can be defined here
  };
  return Cat;
};

Validating That A Numeric Value Is Within A Specified Range

// ./models/cat.js
"use strict";
module.exports = (sequelize, DataTypes) => {
  const Cat = sequelize.define(
    "Cat",
    {
      // ...
      age: {
        type: DataTypes.INTEGER,
        allowNull: false,
        validate: {
          notNull: {
            msg: "age must not be null",
          },
          min: {
            args: [0],
            msg: "age must not be less than zero",
          },
          max: {
            args: [99],
            msg: "age must not be greater than 99",
          },
        },
      },
    },
    {}
  );
  Cat.associate = function (models) {
    // associations can be defined here
  };
  return Cat;
};

Validating That An Attribute Is Among A Finite Set Of Values

// ./models/cat.js
'use strict';
module.exports = (sequelize, DataTypes) => {
  const Cat = sequelize.define('Cat', {
    // ...
    specialSkill: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        notNull: {
          msg: "specialSkill must not be null",
        },
        notEmpty: {
          msg: "specialSkill must not be empty",
        },
        isIn: {
          args: [["jumping", "sleeping", "purring"]],
          msg: "specialSkill must be either jumping, sleeping, or purring",
        },
      },
    },
    // ...
  }, {});
  Cat.associate = function(models) {
    // associations can be defined here
  };
  return Cat;

Transactions With Sequelize

The Problem: Database Updates Can Fail Database updates can fail for a large number of reasons:

  1. Command get’s sent but the DB has been shut down by the database administrator.
  2. Bug in the database cases the system to crash.
  3. Power loss to the machine powering the DB.
  4. Internet connection disruption.
  5. Update asks the database to violate a pre-defined constraint.

The Solution: Database Transactions The BankAccounts Schema

// ./models/bank_account.js
"use strict";
module.exports = (sequelize, DataTypes) => {
  // Define BankAccount model.
  const BankAccount = sequelize.define(
    "BankAccount",
    {
      // Define clientName attribute.
      clientName: {
        type: DataTypes.STRING,
        allowNull: false,
        // Define validations on clientName.
        validate: {
          // clientName must not be null.
          notNull: {
            msg: "clientName must not be NULL",
          },
          // clientName must not be empty.
          notEmpty: {
            msg: "clientName must not be empty",
          },
        },
      },

      // Define balance attribute.
      balance: {
        type: DataTypes.INTEGER,
        allowNull: false,
        // Define validations on balance.
        validate: {
          // balance must not be less than zero.
          min: {
            args: [0],
            msg: "balance must not be less than zero",
          },
        },
      },
    },
    {}
  );

  return BankAccount;
};

Example: An Update Fails Because Of Validation Failure

// ./index.js
const { sequelize, BankAccount } = require("./models");

// This code will try to transfer $7,500 from Markov to Curie.
async function main() {
  // Fetch Markov and Curie's accounts.
  const markovAccount = await BankAccount.findByPk(1);
  const curieAccount = await BankAccount.findByPk(2);

  try {
    // Increment Curie's balance by $7,500.
    curieAccount.balance += 7500;
    await curieAccount.save();

    // Decrement Markov's balance by $7,500.
    markovAccount.balance -= 7500;
    await markovAccount.save();
  } catch (err) {
    // Report if anything goes wrong.
    console.log("Error!");

    for (const e of err.errors) {
      console.log(`${e.instance.clientName}: ${e.message}`);
    }
  }

  await sequelize.close();
}

main();

Incorrect Solutions

Using A Database Transaction With Sequelize

// ./index.js
const { sequelize, BankAccount } = require("./models");

async function main() {
  // Fetch Markov and Curie's accounts.
  const markovAccount = await BankAccount.findByPk(1);
  const curieAccount = await BankAccount.findByPk(2);

  try {
    await sequelize.transaction(async (tx) => {
      // Increment Curie's balance by $7,500.
      curieAccount.balance += 7500;
      await curieAccount.save({ transaction: tx });

      // Decrement Markov's balance by $7,500.
      markovAccount.balance -= 7500;
      await markovAccount.save({ transaction: tx });
    });
  } catch (err) {
    // Report if anything goes wrong.
    console.log("Error!");

    for (const e of err.errors) {
      console.log(`${e.instance.clientName}: ${e.message}`);
    }
  }

  await sequelize.close();
}

main();

Aside: What Is The Transaction Object?

Transactions Prevent Race Conditions