knex、prisma、sequelize、typeorm は、Node.js アプリケーションでデータベースを操作するための主要なライブラリです。knex は SQL クエリビルダーであり、生の SQL に近い制御を提供します。prisma、sequelize、typeorm は ORM(オブジェクト関係マッピング)であり、データベーステーブルをコード内のオブジェクトとして扱います。これらはデータ取得、保存、スキーマ管理、マイグレーションなどの機能を提供し、アプリケーションのバックエンド層や BFF(Backend For Frontend)層で広く利用されています。
knex、prisma、sequelize、typeorm は、Node.js でデータベースを扱うための代表的なツールですが、それぞれの設計思想と役割が異なります。knex はクエリビルダーであり、残りの 3 つは ORM です。この違いが、コードの書き方、型安全性、そしてメンテナンス性に大きな影響を与えます。実務で直面する一般的な課題を通じて、これらの違いを明確にしていきます。
データベースの構造をどのように定義するかは、プロジェクトの進行方針に直結します。
prisma はスキーマファイルを単一の信頼できる情報源(Single Source of Truth)とします。
schema.prisma でモデルを定義し、そこからデータベースと型を生成します。// prisma/schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
sequelize はコードファーストアプローチを採用しています。
// sequelize/models/user.js
const { DataTypes } = require('sequelize');
const User = sequelize.define('User', {
email: { type: DataTypes.STRING, unique: true },
name: { type: DataTypes.STRING }
});
typeorm もコードファーストですが、デコレータを使用します。
// typeorm/entity/User.ts
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column({ nullable: true })
name: string;
}
knex はスキーマ定義を強制しません。
// knex/migrations/20230101_create_users.js
exports.up = function(knex) {
return knex.schema.createTable('users', (table) => {
table.increments('id');
table.string('email').unique();
table.string('name');
});
};
データを読み取る際の構文は、日常開発で最も触れる部分です。
prisma は型安全なメソッドチェーンを提供します。
// prisma
const user = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true }
});
sequelize はオブジェクトベースのクエリ構文です。
where 句や include をオブジェクトで定義します。// sequelize
const user = await User.findOne({
where: { id: 1 },
include: [{ model: Post }]
});
typeorm はリポジトリパターンまたはクエリビルダーを使います。
find メソッドでシンプルに取得できます。createQueryBuilder を使用します。// typeorm
const user = await userRepository.findOne({
where: { id: 1 },
relations: ['posts']
});
knex は SQL に近いビルダー構文です。
// knex
const user = await knex('users')
.where('id', 1)
.first();
フロントエンド開発者が TypeScript を利用する際、バックエンドの型安全性も重要です。
prisma は生成されるクライアントが完全に型付けされています。
// prisma
// TypeScript で id の型が number であることが保証される
const user = await prisma.user.findUnique({ where: { id: 1 } });
typeorm は TypeScript と親和性が高いです。
prisma ほど厳密ではありません。// typeorm
// Entity クラスが型として機能する
const user: User = await userRepository.findOne({ where: { id: 1 } });
sequelize は型定義ファイルを提供しています。
// sequelize
// 型定義をインポートして使用する必要がある
import { User } from './models';
const user = await User.findOne({ where: { id: 1 } });
knex は型安全性が最も低いです。
// knex
// 戻り値の型は手動でアサーションするか、汎用型を使う
const user = await knex('users').where('id', 1).first();
本番環境でデータベース構造を変更する際の手順も重要です。
prisma は専用ツールを内蔵しています。
prisma migrate コマンドで管理します。# prisma
npx prisma migrate dev --name init
typeorm もマイグレーション機能を内蔵しています。
# typeorm
npm run typeorm -- migration:generate -n MigrationName
sequelize は CLI ツールを別途利用します。
sequelize-cli をインストールして使用します。# sequelize
npx sequelize-cli migration:generate --name create-users
knex もマイグレーション機能を内蔵しています。
# knex
npx knex migrate:make create_users
これら 4 つのライブラリは異なるアプローチを取っていますが、共通する目的と機能も持っています。
// すべてで環境変数から接続情報を取得するのが一般的
const dbUrl = process.env.DATABASE_URL;
// prisma
await prisma.$transaction([...queries]);
// knex
await knex.transaction(async (trx) => { ... });
// sequelize
await sequelize.transaction(async (t) => { ... });
// typeorm
await dataSource.manager.transaction(async (manager) => { ... });
knex、sequelize、typeorm は特に多くの方言に対応しています。// 設定でデータベース種類を切り替え可能
dialect: 'postgres' // または 'mysql', 'sqlite'
// 各パッケージ固有の DevTools や拡張機能が存在する
// prisma: Prisma Studio
// typeorm: TypeORM CLI
| 機能 | prisma | typeorm | sequelize | knex |
|---|---|---|---|---|
| 種類 | ORM | ORM | ORM | クエリビルダー |
| スキーマ定義 | 専用ファイル | デコレータ | コード定義 | マイグレーション |
| 型安全性 | ⭐⭐⭐⭐⭐ (自動生成) | ⭐⭐⭐⭐ (デコレータ) | ⭐⭐⭐ (定義ファイル) | ⭐⭐ (手動) |
| 学習コスト | 低い | 中 | 中 | 高い (SQL 知識必要) |
| クエリ制御 | 制限あり | 柔軟 | 柔軟 | 非常に高い |
| マイグレーション | 内蔵 | 内蔵 | CLI 必要 | 内蔵 |
prisma は、モダンな TypeScript プロジェクトにおいて最もバランスの取れた選択肢です 🧰。型安全性と開発速度を両立したい場合、特に新規プロジェクトでは第一候補になります。データベース構造をコードで明確に管理したいチームに最適です。
typeorm は、既存の Java 経験者や、デコレータベースの設計を好む場合に適しています 🔧。NestJS などのフレームワークと組み合わせることで、その真価を発揮します。
sequelize は、安定性と実績を重視する場合に選ばれます 🛡️。長年維持されているプロジェクトや、複数のデータベース方言を扱う必要があるレガシーシステムでの利用に向いています。
knex は、ORM の制約を受けたくない場合に使用します ⚙️。パフォーマンスが критичな部分や、複雑な SQL クエリを直接制御したい場合に適していますが、型安全性の確保は開発者の責任になります。
結論:型安全性と開発体験を優先するなら prisma、既存の資産や特定のフレームワーク依存なら typeorm または sequelize、SQL 制御を最優先するなら knex を選定してください。
SQL への細かい制御が必要な場合や、既存のデータベース構造に合わせて柔軟にクエリを組み立てたい場合に knex を選択します。ORM のオーバーヘッドを避け、パフォーマンスを最優先するプロジェクトや、複雑な集計クエリを頻繁に実行する場合に適しています。ただし、型安全性やオブジェクトマッピングは自分で実装する必要があります。
型安全性と開発者体験を最優先する場合に prisma を選択します。スキーマファイルから自動生成される型付きクライアントにより、リファクタリングが安全に行えます。モダンな開発環境で、データベース構造をコードで管理したいチームに最適です。ただし、複雑な動的クエリや特定のデータベース機能には制限がある場合があります。
長年安定して維持されている ORM が必要な場合や、複数のデータベース方言(MySQL、PostgreSQL、SQLite など)を同じコードベースで扱いたい場合に sequelize を選択します。機能数が多く、コミュニティも大きいため、情報が見つかりやすい利点があります。コードファーストでモデルを定義するスタイルに慣れているチームに向いています。
Java の Hibernate や NestJS などのフレームワークとの親和性を重視する場合に typeorm を選択します。デコレータを使ったエンティティ定義や、アクティブレコードパターンとデータマッパーパターンの両方をサポートしています。大規模なエンタープライズアプリケーションで、明確な構造を求めるときに適しています。
A SQL query builder that is flexible, portable, and fun to use!
A batteries-included, multi-dialect (PostgreSQL, MariaDB, MySQL, CockroachDB, MSSQL, SQLite3, Oracle (including Oracle Wallet Authentication)) query builder for Node.js, featuring:
Node.js versions 16+ are supported.
You can report bugs and discuss features on the GitHub issues page or send tweets to @kibertoad.
For support and questions, join our Gitter channel.
For knex-based Object Relational Mapper, see:
To see the SQL that Knex will generate for a given query, you can use Knex Query Lab
Node.js 16+
Python 3.x with setuptools installed (required for building native dependencies like better-sqlite3)
Python 3.12+ removed the built-in distutils module. If you encounter a ModuleNotFoundError: No module named 'distutils' error during npm install, install setuptools for the Python version used by node-gyp:
pip install setuptools
Windows only: Visual Studio Build Tools with the "Desktop development with C++" workload
npm install
We have several examples on the website. Here is the first one to get you started:
const knex = require('knex')({
client: 'sqlite3',
connection: {
filename: './data.db',
},
});
try {
// Create a table
await knex.schema
.createTable('users', (table) => {
table.increments('id');
table.string('user_name');
})
// ...and another
.createTable('accounts', (table) => {
table.increments('id');
table.string('account_name');
table.integer('user_id').unsigned().references('users.id');
});
// Then query the table...
const insertedRows = await knex('users').insert({ user_name: 'Tim' });
// ...and using the insert id, insert into the other table.
await knex('accounts').insert({
account_name: 'knex',
user_id: insertedRows[0],
});
// Query both of the rows.
const selectedRows = await knex('users')
.join('accounts', 'users.id', 'accounts.user_id')
.select('users.user_name as user', 'accounts.account_name as account');
// map over the results
const enrichedRows = selectedRows.map((row) => ({ ...row, active: true }));
// Finally, add a catch statement
} catch (e) {
console.error(e);
}
import { Knex, knex } from 'knex';
interface User {
id: number;
age: number;
name: string;
active: boolean;
departmentId: number;
}
const config: Knex.Config = {
client: 'sqlite3',
connection: {
filename: './data.db',
},
useNullAsDefault: true,
};
const knexInstance = knex(config);
knexInstance<User>('users')
.select()
.then((users) => {
console.log(users);
})
.catch((err) => {
console.error(err);
})
.finally(() => {
knexInstance.destroy();
});
If you are launching your Node application with --experimental-modules, knex.mjs should be picked up automatically and named ESM import should work out-of-the-box.
Otherwise, if you want to use named imports, you'll have to import knex like this:
import { knex } from 'knex/knex.mjs';
You can also just do the default import:
import knex from 'knex';
If you are not using TypeScript and would like the IntelliSense of your IDE to work correctly, it is recommended to set the type explicitly:
/**
* @type {Knex}
*/
const database = knex({
client: 'mysql',
connection: {
host: '127.0.0.1',
user: 'your_database_user',
password: 'your_database_password',
database: 'myapp_test',
},
});
database.migrate.latest();