Skip to content

ORM (Sequelize) — Basics

Maps relational rows ↔ object instances. Generates SQL from method calls (User.findAll, user.save). Handles connection pooling, type coercion, lifecycle hooks, transactions.

  • Supports Postgres, MySQL/MariaDB, MSSQL, SQLite, Snowflake, DB2, Oracle.
  • v6 = current stable; v7 alpha (full TS rewrite).
  • Models defined via Model.init(attrs, options) or @Table decorator (legacy decorators).
  • Model = JS class bound to a table.
  • Attribute = column definition (type, allowNull, defaultValue, validate).
  • Association = belongsTo, hasOne, hasMany, belongsToMany.
  • Instance = single row in memory; has save(), destroy(), reload(), changed(), previous().
  • Finder = findOne, findAll, findByPk, findOrCreate, findAndCountAll.
  • Scope = named pre-canned where/include fragments.
  • sync() reads model files, emits CREATE TABLE IF NOT EXISTS. Not versioned, not reversible. Dev only.
  • sequelize-cli migrations = ordered files in migrations/, tracked in SequelizeMeta. Use in CI/prod.
  • sync({ alter: true }) silently drops columns missing from models. Never run in prod.
Hook familyFires
beforeValidate / afterValidateBefore / after model validation
beforeCreate / afterCreateSingle-row insert
beforeUpdate / afterUpdateSingle-row update
beforeDestroy / afterDestroySingle-row delete
beforeSave / afterSaveBoth create + update
beforeBulkCreate / beforeBulkUpdate / beforeBulkDestroyBulk ops (instance hooks skip!)

Pass individualHooks: true to force per-row hooks on bulk ops (expensive — loads all rows).

  • bulkCreate skips beforeCreate by default. Password hashing in beforeCreate → plaintext in CSV imports.
  • Raw queries (sequelize.query) bypass hooks, paranoid filters, default scopes.
  • Forgetting { transaction: t } runs the query on a separate connection — silent atomicity break.
  • paranoid: true doesn’t cascade. Orphan FKs after soft delete.
  • unique index sees soft-deleted rows. Need partial unique WHERE deleted_at IS NULL (Postgres) or generated-column trick (MySQL).
  • findAndCountAll with includes inflates count to joined row count. Use distinct: true.