Sequelize の使い方完全ガイド — Node.js 定番ORM
一言でいうと
Sequelize は、Node.js 向けの Promise ベース ORM(Object-Relational Mapper)です。PostgreSQL、MySQL、MariaDB、SQLite、Microsoft SQL Server、DB2、Snowflake など主要なリレーショナルデータベースを統一的なAPIで操作でき、トランザクション、リレーション、Eager/Lazy Loading、リードレプリケーションなど本格的な機能を備えています。
どんな時に使う?
- Express / Fastify などの Node.js バックエンドで、RDB を扱うアプリケーションを構築するとき — 生SQLを書かずにモデル定義・CRUD・マイグレーションを管理したい場合
- 複数のデータベースエンジンに対応する必要があるとき — 開発環境は SQLite、本番は PostgreSQL のように切り替えたい場合
- テーブル間のリレーション(1対多、多対多など)を宣言的に定義し、JOIN を含む複雑なクエリを安全に組み立てたいとき
インストール
Sequelize 本体に加え、使用するデータベースに対応するドライバパッケージが必要です。
# npm
npm install sequelize
# yarn
yarn add sequelize
# pnpm
pnpm add sequelize
データベースドライバ(使用するDBに応じて1つ選択)
# PostgreSQL
npm install pg pg-hstore
# MySQL / MariaDB
npm install mysql2
# SQLite
npm install sqlite3
# Microsoft SQL Server
npm install tedious
# DB2
npm install ibm_db
補足: 本記事は Sequelize v6(6.37.x)を対象としています。次期メジャーバージョン(v7)では API が大きく変わる可能性があります。
基本的な使い方
最もよく使うパターンとして、接続の確立 → モデル定義 → CRUD 操作の流れを示します。
import { Sequelize, DataTypes, Model, Optional } from 'sequelize';
// 1. 接続の確立
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'postgres', // 'mysql' | 'mariadb' | 'sqlite' | 'mssql' | 'db2'
logging: false, // SQLログを無効化(開発時は console.log を推奨)
});
// 2. モデルの型定義
interface UserAttributes {
id: number;
name: string;
email: string;
isActive: boolean;
}
interface UserCreationAttributes extends Optional<UserAttributes, 'id'> {}
// 3. モデルクラスの定義
class User extends Model<UserAttributes, UserCreationAttributes> implements UserAttributes {
declare id: number;
declare name: string;
declare email: string;
declare isActive: boolean;
declare readonly createdAt: Date;
declare readonly updatedAt: Date;
}
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
},
email: {
type: DataTypes.STRING(255),
allowNull: false,
unique: true,
validate: {
isEmail: true,
},
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
},
{
sequelize,
tableName: 'users',
timestamps: true,
}
);
// 4. テーブル同期 & CRUD 操作
async function main() {
// 接続テスト
await sequelize.authenticate();
console.log('接続成功');
// テーブル作成(開発用。本番ではマイグレーションを使用)
await sequelize.sync({ alter: true });
// CREATE
const user = await User.create({
name: '田中太郎',
email: 'tanaka@example.com',
});
console.log('作成:', user.toJSON());
// READ
const found = await User.findByPk(user.id);
console.log('取得:', found?.toJSON());
// UPDATE
await User.update({ name: '田中次郎' }, { where: { id: user.id } });
// DELETE
await User.destroy({ where: { id: user.id } });
await sequelize.close();
}
main().catch(console.error);
よく使うAPI
1. クエリ — findAll / findOne / findAndCountAll
// 条件付き検索
import { Op } from 'sequelize';
const activeUsers = await User.findAll({
where: {
isActive: true,
name: {
[Op.like]: '%田中%',
},
},
order: [['createdAt', 'DESC']],
limit: 20,
offset: 0,
});
// ページネーション向き(総件数 + データを同時取得)
const { count, rows } = await User.findAndCountAll({
where: { isActive: true },
limit: 10,
offset: 0,
});
console.log(`全${count}件中、${rows.length}件取得`);
// 1件だけ取得
const singleUser = await User.findOne({
where: { email: 'tanaka@example.com' },
});
2. リレーション(アソシエーション)
class Post extends Model {
declare id: number;
declare title: string;
declare content: string;
declare userId: number;
}
Post.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
title: {
type: DataTypes.STRING(200),
allowNull: false,
},
content: {
type: DataTypes.TEXT,
allowNull: false,
},
userId: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{ sequelize, tableName: 'posts' }
);
// アソシエーション定義
User.hasMany(Post, { foreignKey: 'userId', as: 'posts' });
Post.belongsTo(User, { foreignKey: 'userId', as: 'author' });
// Eager Loading(JOINで一括取得)
const usersWithPosts = await User.findAll({
include: [
{
model: Post,
as: 'posts',
where: { title: { [Op.like]: '%Sequelize%' } },
required: false, // LEFT JOIN(false)/ INNER JOIN(true)
},
],
});
3. トランザクション
// マネージドトランザクション(推奨:自動コミット/ロールバック)
await sequelize.transaction(async (t) => {
const user = await User.create(
{ name: '佐藤花子', email: 'sato@example.com' },
{ transaction: t }
);
await Post.create(
{ title: '初投稿', content: 'こんにちは!', userId: user.id },
{ transaction: t }
);
// 例外が発生すると自動ロールバック
});
// アンマネージドトランザクション(手動制御)
const t = await sequelize.transaction();
try {
await User.create({ name: 'テスト', email: 'test@example.com' }, { transaction: t });
await t.commit();
} catch (error) {
await t.rollback();
throw error;
}
4. 生SQL(Raw Query)
import { QueryTypes } from 'sequelize';
// SELECT
const results = await sequelize.query<{ id: number; name: string }>(
'SELECT id, name FROM users WHERE is_active = :isActive',
{
replacements: { isActive: true },
type: QueryTypes.SELECT,
}
);
// バインドパラメータ($1 形式)
const results2 = await sequelize.query(
'SELECT * FROM users WHERE id = $1',
{
bind: [1],
type: QueryTypes.SELECT,
}
);
5. スコープとバリデーション
class Product extends Model {
declare id: number;
declare name: string;
declare price: number;
declare status: 'active' | 'archived';
}
Product.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true,
len: [1, 200],
},
},
price: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
validate: {
min: 0,
},
},
status: {
type: DataTypes.ENUM('active', 'archived'),
defaultValue: 'active',
},
},
{
sequelize,
tableName: 'products',
// スコープ定義:よく使う条件をプリセット
scopes: {
active: {
where: { status: 'active' },
},
expensive: {
where: { price: { [Op.gte]: 10000 } },
},
},
}
);
// スコープの利用
const activeProducts = await Product.scope('active').findAll();
const expensiveActiveProducts = await Product.scope(['active', 'expensive']).findAll();
類似パッケージとの比較
| 特徴 | Sequelize | TypeORM | Prisma | Knex.js |
|---|---|---|---|---|
| 種別 | ORM | ORM | ORM + クエリビルダ | クエリビルダ |
| TypeScript サポート | △(型定義は手動) | ◎(デコレータベース) | ◎(スキーマから自動生成) | ○ |
| 対応DB数 | 7+ | 7+ | 6+ | 5+ |
| マイグレーション | CLI別パッケージ | 内蔵 | 内蔵 | 内蔵 |
| 学習コスト | 中 | 中〜高 | 低〜中 | 低 |
| コミュニティ規模 | 非常に大きい(歴史が長い) | 大きい | 急成長中 | 大きい |
| Active Record パターン | ✅ | ✅(両対応) | ❌(Data Mapper的) | ❌ |
| 生SQLの書きやすさ | ○ | ○ | ○($queryRaw) | ◎ |
選定の目安:
- 既存プロジェクトで実績重視 → Sequelize
- TypeScript ファーストで型安全を最優先 → Prisma
- デコレータベースで Entity を書きたい → TypeORM
- ORM不要、SQLに近い操作がしたい → Knex.js
注意点・Tips
1. 本番環境では sync() を使わない
// ❌ 本番で絶対にやってはいけない
await sequelize.sync({ force: true }); // テーブルを DROP して再作成
// ✅ マイグレーションを使う
// npx sequelize-cli db:migrate
sequelize.sync() は開発・プロトタイピング専用です。本番環境では必ず sequelize-cli のマイグレーション機能を使いましょう。
2. N+1 問題に注意
// ❌ N+1 が発生するパターン
const users = await User.findAll();
for (const user of users) {
const posts = await Post.findAll({ where: { userId: user.id } }); // N回クエリ発行
}
// ✅ Eager Loading で1回(または2回)のクエリに
const users = await User.findAll({
include: [{ model: Post, as: 'posts' }],
});
3. TypeScript での型安全を強化する
v6 の TypeScript サポートは手動での型定義が必要で冗長です。より快適に使いたい場合は sequelize-typescript の導入を検討してください。
npm install sequelize-typescript
4. コネクションプールの設定
デフォルトのプール設定は小規模向けです。本番環境ではワークロードに合わせて調整しましょう。
const sequelize = new Sequelize('database', 'user', 'pass', {
dialect: 'postgres',
pool: {
max: 20, // 最大接続数(デフォルト: 5)
min: 5, // 最小接続数(デフォルト: 0)
acquire: 30000, // 接続取得タイムアウト(ms)
idle: 10000, // アイドルタイムアウト(ms)
},
});
5. paranoid モードで論理削除
User.init(
{ /* ... */ },
{
sequelize,
tableName: 'users',
paranoid: true, // deletedAt カラムが追加され、destroy() が論理削除になる
}
);
// 論理削除(deletedAt に日時がセットされる)
await user.destroy();
// 論理削除されたレコードも含めて検索
const allUsers = await User.findAll({ paranoid: false });
// 物理削除
await user.destroy({ force: true });
6. Op のインポートを忘れない
Sequelize v5 以降、文字列ベースの演算子($gt など)はデフォルトで無効です。必ず Op シンボルを使いましょう。
import { Op } from 'sequelize';
//