node-postgres を使用した ORM オーバーヘッドを回避する直接データベース操作
Ethan Miller
Product Engineer · Leapcell

はじめに
JavaScript バックエンド開発、特に Node.js の活気ある世界では、データベースとの対話は基本的なタスクです。長らく、オブジェクト・リレーショナル・マッパー(ORM)は、オブジェクト指向インターフェイスの背後にある SQL を抽象化することでデータベース操作を簡素化することを約束する、定番のソリューションでした。Sequelize や TypeORM のようなフレームワークは、強力な機能(スキーマ移行、モデル関連付け、クエリビルダーなど)を提供するため、非常に人気があります。
しかし、この利便性にはしばしば代償が伴います。ORM は、すべてのプロジェクトに必要とは限らない複雑さ、定型処理、パフォーマンスのオーバーヘッドをもたらす可能性があります。これにより、重要な疑問が生じます。ORM を避け、データベースに直接対話する方が良いのはいつでしょうか?この記事では、PostgreSQL ユーザーにとって説得力のある代替案、つまり node-postgres(pg)ネイティブライブラリについて掘り下げます。多くの一般的なユースケースでは、pg を直接使用することが、よりクリーンで、よりパフォーマンスが高く、最終的により保守性の高いコードにつながり、多くの場合 ORM を不要な抽象化にすることを示します。
コアコンセプトの理解
node-postgres の実際的な側面に踏み込む前に、この議論に不可欠ないくつかのコア用語を明確にしましょう。
- SQL(Structured Query Language): リレーショナルデータベースを管理および操作するための標準言語です。データベースにどのデータを保存、取得、更新、または削除するかを指示する方法です。
- データベースドライバー/クライアント: アプリケーションが特定の種類のデータベースに接続して対話できるようにするソフトウェアコンポーネントです。
node-postgres(しばしばpgと呼ばれる)は、Node.js 用の公式 PostgreSQL クライアントです。これにより、SQL クエリを PostgreSQL サーバーに送信し、結果を受信することが容易になります。 - ORM(Object-Relational Mapper): オブジェクト指向プログラミング言語を使用して、互換性のない型システム間のデータを変換するプログラミングツールです。ORM は基本的にデータベーステーブルをクラスに、テーブル行をオブジェクトにマッピングし、開発者が生の SQL を書く代わりに、好みのプログラミング言語のオブジェクトを使用してデータベースと対話できるようにします。
- 生の SQL: ORM のクエリビルダーの抽象化レイヤーなしで、開発者が直接書いた SQL ステートメント。
Node-Postgres がすべてをまかなえる理由
node-postgres を直接使用する主な利点は、制御と明確さです。生の SQL を書くとき、実行されるクエリに対して絶対的な制御を持つことができます。これにより、いくつかの利点が得られます。
- 直接通信: データベースのネイティブ言語で話しています。これにより、特に複雑なクエリの場合、ORM のクエリジェネレーターによって導入される可能性のある誤解や非効率性が排除されます。
- パフォーマンスの透明性: クエリのパフォーマンスを理解し、最適化することが容易になります。データベースに送信されている SQL を正確に確認できるため、正確な調整とデバッグが可能になります。ORM の場合、生成された SQL を理解するには、追加のロギングまたはデバッグツールが必要になることがあります。
- 抽象化オーバーヘッドの削減: ORM は抽象化レイヤーを追加します。これは、一部には役立ちますが、他の人には過剰かもしれません。この抽象化は、場合によっては「N+1 クエリ問題」を引き起こしたり、最適とは言えない SQL を生成したりすることがあり、それを修正するために ORM の抽象化を「エスケープ」する方法を理解する必要があります。
pgを使用すると、これが完全に回避されます。 - 依存関係のフットプリントの縮小: ORM は多くの場合、多くの依存関係を持つ大きなライブラリです。
pgは比較軽量なドライバーであり、node_modulesディレクトリが小さくなり、ビルド時間が短縮される可能性があります。 - データベース機能の活用: SQL を直接書くことで、ORM のサポートを待ったり、ORM のクエリビルダーに無理やり組み込もうとしたりすることなく、高度なデータベース固有の機能(共通テーブル式、ウィンドウ関数、ユーザー定義関数など)を簡単に利用できます。
Node-Postgres の使用方法
実例を挙げて説明しましょう。まず、ライブラリをインストールする必要があります。
npm install pg
次に、接続を確立します。単一クエリには Client を、ほとんどのアプリケーションで推奨される複数の同時接続を管理するには Pool を使用できます。
接続プールの確立
// db.js const { Pool } = require('pg'); const pool = new Pool({ user: 'your_user', host: 'localhost', database: 'your_database', password: 'your_password', port: 5432, }); pool.on('error', (err, client) => { console.error('アイドルクライアントでの予期しないエラー', err); process.exit(-1); }); module.exports = { query: (text, params) => pool.query(text, params), getClient: () => pool.connect(), };
基本的な CRUD 操作の実行
簡単な users テーブルを想像してみましょう。
CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP );
1. データの挿入
const { query } = require('./db'); async function createUser(name, email) { const text = 'INSERT INTO users(name, email) VALUES($1, $2) RETURNING id, name, email'; const values = [name, email]; try { const res = await query(text, values); console.log('ユーザー作成:', res.rows[0]); return res.rows[0]; } catch (err) { console.error('ユーザー作成エラー:', err.stack); throw err; } } // 使用例 createUser('Alice Smith', 'alice@example.com');
$1、$2 プレースホルダーに注目してください。pg は、SQL インジェクション攻撃を防ぐために不可欠なパラメータ化クエリを使用します。
2. データの取得
const { query } = require('./db'); async function getUsers() { const text = 'SELECT id, name, email, created_at FROM users'; try { const res = await query(text); console.log('全ユーザー:', res.rows); return res.rows; } catch (err) { console.error('ユーザー取得エラー:', err.stack); throw err; } } async function getUserById(id) { const text = 'SELECT id, name, email, created_at FROM users WHERE id = $1'; const values = [id]; try { const res = await query(text, values); console.log(`ID ${id} のユーザー:`, res.rows[0]); return res.rows[0]; } catch (err) { console.error(`ID ${id} のユーザー取得エラー:`, err.stack); throw err; } } // 使用例 getUsers(); getUserById(1);
3. データの更新
const { query } = require('./db'); async function updateUserEmail(id, newEmail) { const text = 'UPDATE users SET email = $1 WHERE id = $2 RETURNING id, name, email'; const values = [newEmail, id]; try { const res = await query(text, values); if (res.rows.length === 0) { console.log(`ID ${id} のユーザーは見つかりませんでした。`); return null; } console.log('ユーザー更新:', res.rows[0]); return res.rows[0]; } catch (err) { console.error(`ID ${id} のユーザー更新エラー:`, err.stack); throw err; } } // 使用例 updateUserEmail(1, 'alice.new@example.com');
4. データの削除
const { query } = require('./db'); async function deleteUser(id) { const text = 'DELETE FROM users WHERE id = $1 RETURNING id'; const values = [id]; try { const res = await query(text, values); if (res.rows.length === 0) { console.log(`ID ${id} のユーザーは見つかりませんでした。`); return null; } console.log(`ID ${res.rows[0].id} のユーザーが削除されました。`); return res.rows[0].id; } catch (err) { console.error(`ID ${id} のユーザー削除エラー:`, err.stack); throw err; } } // 使用例 deleteUser(2);
トランザクション
複数のデータベース変更がすべて成功またはすべて失敗する必要がある操作には、トランザクションが不可欠です。
const { getClient } = require('./db'); async function transferFunds(fromAccountId, toAccountId, amount) { const client = await getClient(); try { await client.query('BEGIN'); // 送金元からの控除 await client.query( 'UPDATE accounts SET balance = balance - $1 WHERE id = $2', [amount, fromAccountId] ); // 受金者への追加 await client.query( 'UPDATE accounts SET balance = balance + $1 WHERE id = $2', [amount, toAccountId] ); await client.query('COMMIT'); console.log(`アカウント ${fromAccountId} からアカウント ${toAccountId} への ${amount} の送金に成功しました。`); return true; } catch (err) { await client.query('ROLLBACK'); console.error('資金移動中のエラー、トランザクションをロールバックします:', err.stack); throw err; } finally { client.release(); // クライアントをプールに戻します } } // 仮の `accounts` テーブル: // CREATE TABLE accounts (id SERIAL PRIMARY KEY, balance NUMERIC); // INSERT INTO accounts (balance) VALUES (1000), (500); // 使用例 transferFunds(1, 2, 100);
アプリケーションシナリオ
小規模から中規模のアプリケーション、データモデルに焦点を当てたマイクロサービス、パフォーマンスや細粒度の SQL 制御が最優先されるプロジェクトは、node-postgres を直接使用する優れた候補です。チームが SQL に慣れている場合、このアプローチはデータ集約型機能の開発を迅速化することがよくあります。
結論
ORM は特定のプロジェクトの開発を迅速化できる説得力のある抽象化を提供しますが、万能のソリューションではありません。PostgreSQL と対話する多くの Node.js アプリケーションでは、node-postgres ネイティブライブラリは、直接的で強力で、しばしばより効率的な代替手段を提供します。pg で生の SQL を採用することにより、開発者は細粒度の制御、透過的なパフォーマンス、および軽量な依存関係のフットプリントを獲得できます。これは、抽象化が少ないほど、より堅牢で保守性の高いデータ対話につながることが多いことを示しています。あなたのプロジェクトは、データベースと直接話すことから恩恵を受けるかもしれません。

