knex の使い方

A batteries-included SQL query & schema builder for PostgresSQL, MySQL, CockroachDB, MSSQL and SQLite3

v3.2.93.6M/週MITORM / DB
AI生成コンテンツ

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

knex(Knex.js)の使い方 — Node.js向けSQLクエリビルダー完全ガイド

一言でいうと

Knex.jsは、PostgreSQL・MySQL・SQLite3・MSSQL・CockroachDBなど複数のデータベースに対応したNode.js向けSQLクエリビルダーです。SQLを直接書く煩雑さを排除しつつ、ORMほどの抽象化は行わない「ちょうどいい」レイヤーを提供します。

どんな時に使う?

  • ORMを使うほどではないが、生SQLは避けたい — チェーンメソッドで型安全にクエリを組み立てたい場合
  • 複数のDBエンジンに対応するアプリケーション — 開発環境はSQLite、本番はPostgreSQLといった構成で、クエリコードを共通化したい場合
  • マイグレーション・シード管理を一元化したい — スキーマのバージョン管理と初期データ投入をコードベースで管理したい場合

インストール

# npm
npm install knex

# yarn
yarn add knex

# pnpm
pnpm add knex

加えて、使用するデータベースのドライバを別途インストールします。

# PostgreSQL
npm install pg

# MySQL / MariaDB
npm install mysql2

# SQLite3
npm install better-sqlite3

# MSSQL
npm install tedious

基本的な使い方

最もよく使うパターンとして、接続の初期化からCRUD操作までを示します。

import { knex, Knex } from 'knex';

// 接続設定
const db = knex({
  client: 'pg',
  connection: {
    host: '127.0.0.1',
    port: 5432,
    user: 'app_user',
    password: 'secret',
    database: 'myapp',
  },
  pool: { min: 2, max: 10 },
});

// テーブルの型定義
interface User {
  id: number;
  name: string;
  email: string;
  created_at: Date;
}

// INSERT
const [inserted] = await db<User>('users')
  .insert({ name: '田中太郎', email: 'tanaka@example.com' })
  .returning('*');

// SELECT
const users = await db<User>('users')
  .where('name', 'like', '%田中%')
  .orderBy('created_at', 'desc')
  .limit(10);

// UPDATE
await db<User>('users')
  .where({ id: 1 })
  .update({ email: 'new@example.com' });

// DELETE
await db<User>('users')
  .where({ id: 1 })
  .del();

// アプリケーション終了時に接続プールを破棄
await db.destroy();

よく使うAPI

1. knex.schema — スキーマビルダー(テーブル作成・変更)

// テーブル作成
await db.schema.createTable('posts', (table) => {
  table.increments('id').primary();
  table.string('title', 255).notNullable();
  table.text('body');
  table.integer('author_id').unsigned().references('id').inTable('users').onDelete('CASCADE');
  table.timestamps(true, true); // created_at, updated_at
  table.index(['author_id', 'created_at']);
});

// テーブル変更
await db.schema.alterTable('posts', (table) => {
  table.boolean('published').defaultTo(false);
  table.dropColumn('body');
});

// テーブル存在チェック
const exists = await db.schema.hasTable('posts');

// テーブル削除
await db.schema.dropTableIfExists('posts');

2. where 系メソッド — 条件指定

// 基本的な条件
const activeUsers = await db<User>('users')
  .where('active', true)
  .andWhere('age', '>=', 20);

// OR条件
const results = await db<User>('users')
  .where('role', 'admin')
  .orWhere('role', 'moderator');

// WHERE IN
const specific = await db<User>('users')
  .whereIn('id', [1, 2, 3]);

// サブクエリ / 複雑な条件のグルーピング
const complex = await db<User>('users')
  .where((builder) => {
    builder.where('age', '>', 30).orWhere('name', 'like', '%VIP%');
  })
  .andWhere('active', true);

// WHERE NULL / NOT NULL
const noEmail = await db<User>('users')
  .whereNull('email');

3. join — テーブル結合

interface Post {
  id: number;
  title: string;
  author_id: number;
}

interface PostWithAuthor {
  post_id: number;
  title: string;
  author_name: string;
}

const postsWithAuthors = await db<Post>('posts')
  .join('users', 'posts.author_id', '=', 'users.id')
  .select<PostWithAuthor[]>(
    'posts.id as post_id',
    'posts.title',
    'users.name as author_name'
  );

// LEFT JOIN
const allUsers = await db('users')
  .leftJoin('posts', 'users.id', 'posts.author_id')
  .select('users.name', db.raw('COUNT(posts.id) as post_count'))
  .groupBy('users.id');

4. transaction — トランザクション

// async/await パターン(推奨)
await db.transaction(async (trx: Knex.Transaction) => {
  const [order] = await trx('orders')
    .insert({ user_id: 1, total: 5000 })
    .returning('id');

  await trx('order_items').insert([
    { order_id: order.id, product_id: 10, quantity: 2 },
    { order_id: order.id, product_id: 20, quantity: 1 },
  ]);

  await trx('inventory')
    .where('product_id', 10)
    .decrement('stock', 2);

  // 例外が発生すると自動的にロールバック
});

5. migrate / seed — マイグレーションとシード

# CLIのセットアップ(グローバルまたはnpx経由)
npx knex init           # knexfile.js を生成
npx knex migrate:make create_users   # マイグレーションファイル生成
npx knex migrate:latest              # 最新まで適用
npx knex migrate:rollback            # 直前のバッチをロールバック
npx knex seed:make seed_users        # シードファイル生成
npx knex seed:run                    # シード実行

マイグレーションファイルの例(TypeScript):

// migrations/20240101000000_create_users.ts
import { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
  await knex.schema.createTable('users', (table) => {
    table.increments('id').primary();
    table.string('name', 100).notNullable();
    table.string('email', 255).notNullable().unique();
    table.boolean('active').defaultTo(true);
    table.timestamps(true, true);
  });
}

export async function down(knex: Knex): Promise<void> {
  await knex.schema.dropTableIfExists('users');
}

シードファイルの例:

// seeds/seed_users.ts
import { Knex } from 'knex';

export async function seed(knex: Knex): Promise<void> {
  await knex('users').del();
  await knex('users').insert([
    { name: '田中太郎', email: 'tanaka@example.com' },
    { name: '鈴木花子', email: 'suzuki@example.com' },
  ]);
}

類似パッケージとの比較

特徴knexPrismaTypeORMDrizzle ORM
分類クエリビルダーORMORMORM / クエリビルダー
TypeScript対応○(型定義同梱)◎(スキーマから自動生成)◎(デコレータベース)◎(スキーマから型推論)
学習コスト低い中程度高い低〜中
SQLへの近さ非常に近い抽象化が強い中程度近い
マイグレーション内蔵内蔵内蔵内蔵
対応DB数多い(6種以上)多い多い多い
生SQLの書きやすさ◎(knex.raw○($queryRaw
エコシステムObjection.js等のORM基盤独自エコシステム単体で完結単体で完結

選定の目安: SQLに近い操作感を好み、ORMの暗黙的な挙動を避けたいならknex。型安全性を最優先するならPrismaDrizzle。既存のActiveRecord的パターンに慣れているならTypeORM

注意点・Tips

1. SQLite3使用時は useNullAsDefault: true を設定する

SQLite3はデフォルト値の扱いが他のDBと異なるため、設定しないと警告が出ます。

const db = knex({
  client: 'sqlite3',
  connection: { filename: './dev.db' },
  useNullAsDefault: true, // これを忘れない
});

2. returning() はPostgreSQLとMSSQLのみ対応

MySQL/SQLiteでは INSERT ... RETURNING 構文がサポートされていない(MySQL 8.0.21+で部分対応)ため、returning() を使うとエラーになります。DB間の移植性を考慮する場合は注意してください。

3. 接続プールの破棄を忘れない

テストやスクリプトで使う場合、db.destroy() を呼ばないとプロセスが終了しません。

afterAll(async () => {
  await db.destroy();
});

4. knex.raw() でのSQLインジェクション対策

生SQLを使う場合は、必ずバインドパラメータを使用してください。

// ✅ 安全 — バインドパラメータ
const result = await db.raw('SELECT * FROM users WHERE id = ?', [userId]);

// ✅ 安全 — 名前付きバインド
const result2 = await db.raw('SELECT * FROM users WHERE name = :name', { name: userName });

// ❌ 危険 — 文字列結合
const result3 = await db.raw(`SELECT * FROM users WHERE id = ${userId}`);

5. デバッグ時のSQL確認

// クエリチェーンに .toSQL() を付けると、実行せずにSQLを確認できる
const query = db('users').where('active', true).toSQL();
console.log(query.sql);    // "select * from `users` where `active` = ?"
console.log(query.bindings); // [true]

// グローバルにクエリログを有効化
const db = knex({
  client: 'pg',
  connection: { /* ... */ },
  debug: true, // 全クエリをconsoleに出力
});

6. TypeScriptでの型の活用

Knexのジェネリクスはあくまで「ヒント」であり、ランタイムでの型チェックは行われません。厳密な型安全性が必要な場合は、zodなどのバリデーションライブラリと組み合わせることを推奨します。

import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

const rows = await db('users').select('*');
const users = rows.map((row) => UserSchema.parse(row)); // ランタイム検証

まとめ

Knex.jsは、SQLの知識をそのまま活かしながらクエリを安全かつ効率的に組み立てられるクエリビルダーです。マイグレーション・シード・トランザクション・接続プーリングといった実務に必要な機能が一通り揃っており、「バッテリー同梱」の名に恥じない完成度を持っています。ORMの暗黙的な挙動に悩まされたくないが、生SQLの管理コストも下げたいという現場には、今なお有力な選択肢です。

比較記事