NodeJSSequelize ORM

Sequelize ORM

Sequelize is a mature, promise-based ORM for SQL databases (Postgres, MySQL, MariaDB, SQLite, MSSQL). Like Mongoose for MongoDB, it lets you define models and work with objects instead of writing SQL — handling associations, validation, and migrations. It's the most established Node SQL ORM, with a large feature set and a correspondingly large API. This page covers models, associations, querying, and where Sequelize fits relative to Prisma and TypeORM.

Setup and connection

Bash
npm install sequelize pg          # + the driver for your DB (pg, mysql2, ...)

JS
import { Sequelize, DataTypes } from 'sequelize'

export const sequelize = new Sequelize(process.env.DATABASE_URL, {
  dialect: 'postgres',
  pool: { max: 10, idle: 30_000 },     // Sequelize manages the pool
  logging: process.env.NODE_ENV !== 'production' ? console.log : false,
})

await sequelize.authenticate()         // verify the connection at startup
Defining a model

JS
const User = sequelize.define('User', {
  name:  { type: DataTypes.STRING, allowNull: false },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true,
    validate: { isEmail: true },        // model-level validation
  },
  age:   { type: DataTypes.INTEGER, validate: { min: 0, max: 150 } },
  role:  { type: DataTypes.ENUM('user', 'admin'), defaultValue: 'user' },
}, {
  tableName: 'users',
  timestamps: true,                      // adds createdAt / updatedAt
})
A model maps a class to a table; instances are rows
`sequelize.define` creates a model bound to a table — the SQL analog of a [Mongoose model](/nodejs/mongoose). Each instance represents a row, with `.save()`/`.destroy()` methods and access to associations. `DataTypes` map to SQL column types. Built-in `validate` rules run before writes (a second line behind [request validation](/nodejs/validation-intro)).
CRUD

JS
// Create:
const user = await User.create({ name: 'Ada', email: 'ada@x.com' })

// Read:
const all   = await User.findAll({ where: { role: 'user' }, limit: 20, order: [['createdAt', 'DESC']] })
const byPk  = await User.findByPk(req.params.id)
const one   = await User.findOne({ where: { email: 'ada@x.com' } })

// Update (on an instance, or in bulk):
user.name = 'Ada L.'
await user.save()
await User.update({ role: 'admin' }, { where: { id: 1 } })

// Delete:
await user.destroy()
await User.destroy({ where: { active: false } })
Querying with operators

JS
import { Op } from 'sequelize'

await Product.findAll({
  where: {
    price: { [Op.between]: [10, 100] },
    category: { [Op.in]: ['book', 'toy'] },
    name: { [Op.iLike]: '%pro%' },        // case-insensitive (Postgres)
    [Op.or]: [{ featured: true }, { rating: { [Op.gte]: 4.5 } }],
  },
  attributes: ['id', 'name', 'price'],     // projection (like SQL SELECT cols)
})
Operators come from the `Op` symbol object
Sequelize expresses comparisons via the `Op` symbols (`Op.gte`, `Op.in`, `Op.like`) inside the `where` object — these generate parameterized SQL, so they're injection-safe. `attributes` limits the selected columns. The mental model is close to a query builder; the verbosity is the cost of staying database-agnostic.
Associations — the relational core

JS
// Declare relationships; Sequelize manages foreign keys + join helpers:
User.hasMany(Post,   { foreignKey: 'authorId', as: 'posts' })
Post.belongsTo(User, { foreignKey: 'authorId', as: 'author' })

Post.belongsToMany(Tag, { through: 'PostTags' })   // many-to-many via join table
Tag.belongsToMany(Post, { through: 'PostTags' })

// Eager-load associations in one query (a real SQL JOIN):
const posts = await Post.findAll({
  include: [{ model: User, as: 'author', attributes: ['name'] }],
})
posts[0].author.name
`include` is a real JOIN — but over-including causes huge, slow queries
Sequelize's `include` generates an actual SQL `JOIN` (unlike Mongoose's [populate](/nodejs/mongoose-queries), which is a separate query). That's efficient — but deeply nested or many-association includes produce enormous, slow queries and row-multiplication (a post with 50 comments × 10 tags = 500 rows). Include only what you need, project association `attributes`, and split into separate queries when a join explodes. Define associations with explicit `as` aliases to keep results predictable.
Transactions

JS
// Managed transaction — auto commit/rollback around the callback:
await sequelize.transaction(async (t) => {
  await Account.decrement('balance', { by: 100, where: { id: 1 }, transaction: t })
  await Account.increment('balance', { by: 100, where: { id: 2 }, transaction: t })
  // throw anywhere → automatic ROLLBACK; otherwise auto COMMIT
})
Pass `{ transaction: t }` to every query in the transaction
Sequelize's managed transaction commits if the callback resolves and rolls back if it throws — no manual `BEGIN`/`COMMIT`. The catch: **every** query inside must receive `{ transaction: t }`, or it runs outside the transaction and won't be rolled back. Forgetting it on one statement is a subtle, dangerous bug. More on [transactions](/nodejs/transactions).
Migrations, not `sync`

JS
// Convenient in dev only — NEVER in production:
await sequelize.sync({ alter: true })   // tries to reshape tables to match models

// Production: use sequelize-cli migrations (versioned, reviewable, reversible).
`sequelize.sync({ alter/force })` can destroy data — use migrations in production
`sync({ force: true })` **drops and recreates** tables (total data loss); `sync({ alter: true })` guesses schema changes and can mangle columns. They're handy for prototyping, but production schema changes must go through versioned [migrations](/nodejs/database-migrations) (`sequelize-cli`) that are reviewed, reversible, and run deliberately. Never point `sync` at a production database.
Sequelize vs the alternatives

Sequelize

Prisma

TypeORM

Maturity

Very mature, huge ecosystem

Newer, fast-growing

Mature

TypeScript

Add-on typings, imperfect

Excellent (generated client)

Decorator-based, native TS

Schema source

JS model definitions

A schema.prisma file

Entity classes

Style

Active Record + query API

Generated, type-safe client

Active Record / Data Mapper

Pick Sequelize for maturity in JS; Prisma for TS type-safety
Sequelize is a solid choice for JavaScript projects and teams that value its long track record and breadth. If you're TypeScript-first and want end-to-end type safety from schema to query, [Prisma](/nodejs/prisma) is usually the stronger modern pick. [TypeORM](/nodejs/typeorm) suits those who prefer decorator-based entity classes. All three abstract the same [Postgres](/nodejs/postgresql)/[MySQL](/nodejs/mysql) drivers underneath.
Next
The TypeScript-first, schema-driven ORM many new projects choose: [Prisma ORM](/nodejs/prisma).