sequelize の使い方

Sequelize is a promise-based Node.js ORM tool for Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server, Amazon Redshift and Snowflake’s Data Cloud. It features solid transaction support, relations, eager and lazy loading, read replication and more.

v6.37.82.5M/週MITORM / DB
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

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();

類似パッケージとの比較

特徴SequelizeTypeORMPrismaKnex.js
種別ORMORMORM + クエリビルダクエリビルダ
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';

//