SQL Databases with Sequelize

Reviewed & published by Brayan K

Sequelize is an ORM for Node that maps JavaScript objects to rows in a SQL database, so you can define models and run create, read, update, and delete operations without writing raw SQL.

Part of the free Node.js course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll define a model, perform CRUD with methods like create, findAll, update, and destroy, link tables with associations, learn why migrations beat sync() in production, and see how Sequelize compares with Mongoose.

What You'll Learn in This Lesson

1️⃣ Defining a Model

A model is a JavaScript description of a table: its name and its columns (with types and rules). You connect to the database once, then sequelize.define(...) creates the model. Each field gets a DataTypes type (STRING, INTEGER, …) and optional constraints like allowNull or unique.

// Sequelize is an ORM — it maps JS objects to SQL table rows.
// Install once:  npm install sequelize sqlite3
const { Sequelize, DataTypes } = require('sequelize');

// Connect. (SQLite here for zero setup; swap the URL for Postgres/MySQL.)
const sequelize = new Sequelize('sqlite::memory:');

// Define a MODEL — this maps to a "Users" table.
const User = sequelize.define('User', {
  name:  { type: DataTypes.STRING, allowNull: false },
  email: { type: DataTypes.STRING, unique: true },
  age:   { type: DataTypes.INTEGER },
});

// sync() creates the table from the model (use migrations in production).
await sequelize.sync();

2️⃣ CRUD Operations

Once you have a model, the four basic operations each map to a method. create inserts a row, findAll and findByPk read, save (or update) writes changes, and destroy deletes. All return Promises, so you await them. Filters go in a where clause, with operators like Op.gt for "greater than".

To see CRUD with real output and no database to install, here's a Map-backed model exposing the same method names:

// A tiny Map-backed "model" with the SAME method names as Sequelize, so you
// can run CRUD with real output without a database installed.

function defineModel() {
  const rows = new Map();
  let nextId = 1;
  return {
    create(data) {
      const row = { id: nextId++, ...data };
      rows.set(row.id, row);
      return row;                       // mirrors await Model.create()
    },
    findAll() {
      return [...rows.values()];        // mirrors await Model.findAll()
    },
    findByPk(id) {
      return rows.get(id) || null;      // mirrors await Model.findByPk(id)
    },
    update(id, changes) {
      const row = rows.get(id);
      if (row) Object.assign(row, changes);
      return row;
    },
    destroy(id) {
      return rows.delete(id);           // mirrors await row.destroy()
    },
  };
}

const User = defineModel();

console.log('Created:', User.create({ name: 'Ada', age: 36 }));
console.log('Created:', User.create({ name: 'Linus', age: 54 }));
User.update(1, { age: 37 });            // Ada has a birthday
User.destroy(2);                        // remove Linus
console.log('findAll:', User.findAll());
console.log('findByPk(1):', User.findByPk(1));
console.log('findByPk(2):', User.findByPk(2));

3️⃣ Associations, Migrations & vs Mongoose

Associations connect tables: User.hasMany(Post) and Post.belongsTo(User) create the foreign key and let you load related rows together. For schema changes, prefer migrations — versioned, reversible scripts run with the Sequelize CLI — over sync(), which can silently alter or drop columns. And remember the big picture: Sequelize is for SQL databases with JOINs and foreign keys, while Mongoose (the previous database lesson) is for MongoDB documents.

// Associations: a User has many Posts; each Post belongs to a User.
User.hasMany(Post);
Post.belongsTo(User);

// Now you can load a user WITH their posts in one call:
const withPosts = await User.findByPk(1, { include: Post });

// --- A migration file (run with: npx sequelize-cli db:migrate) ---
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.addColumn('Users', 'verified', {
      type: Sequelize.BOOLEAN,
      defaultValue: false,
    });
  },
  async down(queryInterface) {
    await queryInterface.removeColumn('Users', 'verified');
  },
};

Your turn. The model below works once you fill in the two method names marked ___. Follow the 👉 hints, then run it.

// 🎯 YOUR TURN — replace each ___ then run it.
function defineModel() {
  const rows = new Map();
  let nextId = 1;
  return {
    create(data) {
      const row = { id: nextId++, ...data };
      rows.set(row.id, row);
      return row;
    },
    findAll() { return [...rows.values()]; },
  };
}
const Book = defineModel();

Book.___({ title: 'Node Mastery' });   // 👉 which method inserts a row? (create)
Book.create({ title: 'SQL Basics' });

console.log(Book.___());               // 👉 which method lists all rows? (findAll)

// ✅ Expected output:
//    [ { id: 1, title: 'Node Mastery' }, { id: 2, title: 'SQL Basics' } ]

Mini-Challenge: A WHERE Filter

No blanks — just a brief and an outline. Add a where-style filter to findAll so you can fetch only the rows that match. Build it, run it, and compare with the example output.

// 🎯 MINI-CHALLENGE: a model with a WHERE-style filter
// 1. defineModel() with create(data) and findAll(where = {}).
// 2. findAll(where) returns only rows where EVERY key in "where" matches.
// 3. Create three tasks (two with done:false, one with done:true).
// 4. Print findAll() (all of them) and findAll({ done: false }) (the open ones).
//
// ✅ Example output:
//    All: [ ...three tasks... ]
//    Open: [ task 1, task 3 ]

// your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Sequelize CRUD

TaskMethod
Define a modelsequelize.define('User', {...})
Create a rowawait User.create({...})
Read allawait User.findAll()
Read by idawait User.findByPk(id)
FilterfindAll({ where: {...} })
Updaterow.save() / User.update()
Deleteawait row.destroy()

🎉 Lesson Complete!

Practice quiz

What is Sequelize?

  • A web server
  • An ORM that maps JS objects to SQL rows
  • A caching layer
  • A test runner

Answer: An ORM that maps JS objects to SQL rows. Sequelize is an Object-Relational Mapper that lets you work with rows as JS objects.

Which method defines a model (a table) in Sequelize?

  • sequelize.create()
  • sequelize.table()
  • sequelize.define()
  • sequelize.model()

Answer: sequelize.define(). sequelize.define('User', {...}) describes a table with typed fields and constraints.

Which Sequelize method inserts a new row?

  • create
  • findAll
  • destroy
  • save

Answer: create. Model.create({...}) inserts a row and returns the created instance.

Which method reads a single row by its primary key?

  • findAll
  • findOne
  • create
  • findByPk

Answer: findByPk. findByPk(id) selects the row matching the given primary key.

Because Sequelize methods return Promises, what must you do?

  • await them inside an async function
  • Call them twice
  • Wrap them in JSON.stringify
  • Run them synchronously

Answer: await them inside an async function. Every model method returns a Promise, so you await it or you log a pending Promise.

Which method removes a row?

  • findAll
  • destroy
  • create
  • update

Answer: destroy. row.destroy() (or Model.destroy with a where) deletes the row.

Where do you put filters like 'age greater than 30' in a query?

  • In the model definition
  • In the connection string
  • In a where clause with operators like Op.gt
  • In a migration

Answer: In a where clause with operators like Op.gt. Filters go in { where: { age: { [Op.gt]: 30 } } } using Sequelize operators.

For production schema changes, what is preferred over sync()?

  • Editing tables by hand
  • Recreating the database
  • Disabling constraints
  • Versioned migrations

Answer: Versioned migrations. Migrations are reversible, source-controlled scripts; sync({alter:true}) can destroy data.

Which association pair links a User to many Posts?

  • User.hasMany(Post) and Post.belongsTo(User)
  • User.embed(Post)
  • User.populate(Post)
  • User.join(Post)

Answer: User.hasMany(Post) and Post.belongsTo(User). hasMany/belongsTo create the foreign key relationship for SQL joins.

Sequelize is for SQL databases; which library targets MongoDB documents instead?

  • Redis
  • Mongoose
  • Knex
  • Prisma

Answer: Mongoose. Mongoose is the document ORM for MongoDB, whereas Sequelize is for relational SQL databases.

Continue this course

Frequently asked questions

What is an ORM and why use Sequelize?

An ORM (Object-Relational Mapper) is a library that lets you work with database rows as plain JavaScript objects instead of writing raw SQL strings. With Sequelize you define a model (User.create({...}), User.findAll()) and it generates the INSERT, SELECT, UPDATE, and DELETE statements for you, handling parameter escaping (which prevents SQL injection), type conversion, and differences between databases. The benefits are productivity and safety; the cost is a layer of abstraction that can hide what SQL is actually running and occasionally produce inefficient queries — so for hot paths you sometimes drop down to raw queries, which Sequelize also supports.

How are Sequelize associations different from Mongoose references?

Both express relationships between records, but they map to different database models. Sequelize is for SQL databases, so associations like User.hasMany(Post) and Post.belongsTo(User) correspond to foreign keys and let you JOIN tables — the relational engine enforces referential integrity. Mongoose is for MongoDB, a document database, where you either embed related data inside a document or store an ObjectId reference and use .populate() to fetch it in a second query; there are no real JOINs or foreign-key constraints. Choose by your database: Sequelize when your data is naturally relational and you want SQL's guarantees, Mongoose when you want flexible, document-shaped data.

Should I use sync() or migrations?

sequelize.sync() inspects your models and creates or alters tables to match — convenient for quick prototypes and local development. But in production it's risky: sync({ alter: true }) can make destructive schema changes, and there's no record of how the schema evolved. Migrations are versioned scripts (run with the sequelize-cli) that describe each schema change explicitly, can be applied and rolled back in order, and are committed to source control so every environment converges to the same schema. The rule of thumb: sync() for throwaway experiments, migrations for any database whose data you care about.

Does Sequelize work with PostgreSQL, MySQL, and SQLite?

Yes. Sequelize supports PostgreSQL, MySQL, MariaDB, SQLite, and Microsoft SQL Server through pluggable dialects. You install the matching driver (for example pg for Postgres, mysql2 for MySQL, sqlite3 for SQLite) and point the connection string at your database; most of your model and query code stays identical across them. This is part of an ORM's appeal — you can develop against an in-memory SQLite database for fast tests and deploy against Postgres in production with minimal changes. Be aware that a few features and data types are dialect-specific, so test against the database you'll actually run.