はじめに
Node.jsでリレーショナルデータベースを扱うとき、生のSQLを書き続けるか、ORMを挟むかで悩んだことはないでしょうか。Sequelizeは、そんな場面で長年選ばれ続けてきたPromiseベースのORMです。
GitHubで29,000スター以上、2,700人を超えるコントリビューターという数字が、実績の厚さを物語っています。2011年に生まれたプロジェクトで、10年以上メンテナンスされ続けているという安心感は、新しいORMにはなかなか出せない強みですね。
正直、Prismaやdrizzle-ormのような後発ORMと比べると「型推論が弱い」と言われがちなSequelizeですが、PostgreSQL・MySQL・MariaDB・SQLite・Microsoft SQL Serverといった主要DBを同一のAPIで扱えて、トランザクションやマイグレーション、コネクションプーリングまで標準機能でカバーしている点は今でも十分に強い武器です。ライセンスはMITなので、商用プロジェクトでも安心して使えます。
Sequelizeとは
Sequelizeは、Node.js向けの「easy-to-use and promise-based ORM tool」です。PostgreSQL、MySQL、MariaDB、SQLite、Microsoft SQL Server、DB2、Snowflake、Oracle DBなど幅広いデータベースに対応しており、堅牢なトランザクションサポート、リレーション定義、Eager/Lazyロード、リードレプリケーションなどの機能を備えています。
現行の安定版はメジャーバージョン6系(パッケージ名はsequelize)で、次世代版としてTypeScriptで書き直された@sequelize/core(v7系)の開発も進んでいます。この記事では、実務で広く使われている安定版のv6系を中心に解説します。
主な特徴
- マルチDB対応 - PostgreSQL、MySQL、MariaDB、SQLite、MSSQLなどを同じAPIで扱える
- モデルベースの設計 - テーブルをJavaScript/TypeScriptのクラスとして定義し、CRUD操作をメソッド呼び出しに変換できる
- 強力なマイグレーション機能 - Sequelize CLIでスキーマ変更を履歴管理できる
- トランザクション・フック対応 -
beforeCreateなどのライフサイクルフックで業務ロジックを差し込める
インストール方法
npm、yarn、pnpmのいずれでもインストールできます。
# npm
npm install sequelize
# yarn
yarn add sequelize
# pnpm
pnpm add sequelize
使用するデータベースに応じて、ドライバーも別途インストールが必要です。
# PostgreSQL
npm install pg pg-hstore
# MySQL / MariaDB
npm install mysql2
# SQLite
npm install sqlite3
# Microsoft SQL Server
npm install tedious
SequelizeはNode.js専用のライブラリで、データベースへのTCP接続を前提とするため、ブラウザ上では動作しません。このあとのサンプルコードはNode.js環境での実行を想定しています。
基本的な使い方
接続の確立
まずはSequelizeインスタンスを作成し、接続を確認します。
const { Sequelize } = require("sequelize");
const sequelize = new Sequelize("database", "username", "password", {
host: "localhost",
dialect: "postgres", // 'mysql' | 'mariadb' | 'sqlite' | 'mssql' など
});
async function main() {
try {
await sequelize.authenticate();
console.log("接続に成功しました");
} catch (error) {
console.error("接続に失敗しました:", error);
}
}
main();
dialectオプションを切り替えるだけで、同じコードのままDBを差し替えられるのがSequelizeの大きな利点です。
モデル定義
sequelize.defineまたはModelクラスの継承でテーブルをモデルとして定義します。
const { DataTypes } = require("sequelize");
const User = sequelize.define("User", {
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
unique: true,
validate: { isEmail: true },
},
age: {
type: DataTypes.INTEGER,
},
});
// テーブルを実際に作成(開発時のみ推奨)
await User.sync({ force: false });
DataTypesでカラムの型やバリデーションルールを宣言的に書けるので、テーブル定義がそのままコードの中でドキュメント代わりになります。
CRUD操作
モデル定義さえ済ませれば、CRUD操作はメソッド呼び出しだけで完結します。
// CREATE
const user = await User.create({
name: "山田太郎",
email: "taro@example.com",
age: 30,
});
// READ
const allUsers = await User.findAll();
const oneUser = await User.findByPk(user.id);
const adults = await User.findAll({
where: { age: { [Sequelize.Op.gte]: 20 } },
});
// UPDATE
await User.update({ age: 31 }, { where: { id: user.id } });
// DELETE
await User.destroy({ where: { id: user.id } });
Op演算子を使うことで、WHERE age >= 20のような条件をSQLを書かずに表現できます。
実践的なユースケース
リレーションの定義とEager Loading
複数テーブルにまたがるデータを扱うアプリでは、モデル間のリレーション定義が欠かせません。SequelizeはhasMany/belongsToなどのメソッドで関連を宣言し、includeオプションでJOINに相当するEager Loadingを行えます。
const User = sequelize.define("User", { name: DataTypes.STRING });
const Post = sequelize.define("Post", { title: DataTypes.STRING });
User.hasMany(Post, { foreignKey: "userId" });
Post.belongsTo(User, { foreignKey: "userId" });
// ユーザーと紐づく投稿を一度に取得
const usersWithPosts = await User.findAll({
include: [{ model: Post }],
});
usersWithPosts.forEach((user) => {
console.log(user.name, user.Posts.length);
});
includeを使わずに1件ずつPostを取得すると、いわゆるN+1問題が発生します。関連するデータをまとめて必要とする場面では、includeでEager Loadingしておくのが定石です。
トランザクションで一連の処理を保証する
在庫を減らしながら注文レコードを作る、といった「複数の更新をすべて成功させたい、あるいは全部なかったことにしたい」処理では、Sequelizeのトランザクション機能が役立ちます。
const { Transaction } = require("sequelize");
async function placeOrder(userId, productId, quantity) {
const result = await sequelize.transaction(async (t) => {
const product = await Product.findByPk(productId, {
transaction: t,
lock: Transaction.LOCK.UPDATE, // 行ロックで競合を防ぐ
});
if (product.stock < quantity) {
throw new Error("在庫が不足しています");
}
await product.decrement("stock", { by: quantity, transaction: t });
const order = await Order.create(
{ userId, productId, quantity },
{ transaction: t }
);
return order;
});
return result;
}
コールバック内で例外を投げると、Sequelizeが自動的にロールバックしてくれます。逆に正常終了すればコミットされるので、try/catchと組み合わせて手動でロールバックを書く必要がありません。
マイグレーションでスキーマ変更を履歴管理する
開発が進むにつれてテーブル構造は変わっていきます。Sequelize CLIを使うと、スキーマ変更を「マイグレーションファイル」として履歴管理でき、チーム開発でも本番反映の手順を統一できます。
# Sequelize CLIのインストールと初期化
npm install --save-dev sequelize-cli
npx sequelize-cli init
# マイグレーションファイルの生成
npx sequelize-cli migration:generate --name add-age-to-users
生成されたファイルにはup(適用時)とdown(取り消し時)の処理を書きます。
// migrations/xxxxxxxxxxxxxx-add-age-to-users.js
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn("Users", "age", {
type: Sequelize.INTEGER,
allowNull: true,
});
},
async down(queryInterface, Sequelize) {
await queryInterface.removeColumn("Users", "age");
},
};
# マイグレーションの実行
npx sequelize-cli db:migrate
# 直前のマイグレーションを取り消す
npx sequelize-cli db:migrate:undo
downをきちんと書いておけば、本番反映後に問題が見つかってもdb:migrate:undoで安全に切り戻せます。
まとめ
Sequelizeを使うと得られるものを整理すると、次のようになります。
- マルチDB対応: PostgreSQL・MySQL・MariaDB・SQLiteなどを同じAPIで扱える
- 宣言的なモデル定義:
DataTypesでスキーマとバリデーションをまとめて表現できる - トランザクション: コールバック形式で自動コミット・自動ロールバックを扱える
- マイグレーション: Sequelize CLIでスキーマ変更を履歴として管理できる
- 実績: 2011年から続く長い運用実績とエコシステム
PrismaやDrizzle ORMのような後発ORMに注目が集まる中でも、Sequelizeは「枯れた安定感」という点で依然として有力な選択肢です。既存プロジェクトの多くがSequelizeで構築されていることもあり、保守フェーズの案件に入るなら知っておいて損はないORMだと思います。
新規プロジェクトで型推論の強さを重視するならPrismaやDrizzle ORMも検討候補になりますが、「複数のSQL系DBを同じコードベースで柔軟に切り替えたい」「枯れたエコシステムに乗りたい」という場面では、Sequelizeが今も現役の選択肢であり続けています。