はじめに
Node.jsからPostgreSQLを触るとき、みなさんは何を使っていますか。pgパッケージが定番ですが、コネクションプールの設定やクエリの書き方でちょっと冗長になりがちですよね。
Postgres.jsは、そんな悩みを「タグ付きテンプレートリテラル」というJavaScriptの言語機能でしれっと解決してしまうライブラリです。GitHubで8,600以上のスターを集めており、bestofjsでも継続的に注目されています。ORMを挟まずに素のSQLを書きたい、でもSQLインジェクション対策や動的クエリ生成は楽をしたい、という人にちょうどいい立ち位置のクライアントです。
Postgres.jsとは
Postgres.jsは、Node.js・Deno・Bun・Cloudflare Workersで動くPostgreSQL専用クライアントです。porsager氏が開発しており、「Fastest full featured PostgreSQL client」を謳っています。
最大の特徴は、SQL文字列をタグ付きテンプレートリテラルとして書く設計です。sql\select * from users where id = ${id}``のように書くだけで、パラメータが自動的にプレースホルダに変換され、SQLインジェクションの心配なく安全にクエリを実行できます。
主な特徴
- タグ付きテンプレートリテラル -
sql\...`` の形でSQLを直接書ける。ORMの独自DSLを覚える必要がない - 自動的なSQLインジェクション対策 - パラメータは別送信されるため、エスケープ処理を書かなくても安全
- 動的クエリ生成 - カラム名や挿入データを動的に組み立てる
sql()ヘルパーが強力 - マルチランタイム対応 - Node.js、Deno、Bun、Cloudflare Workersで動作
- 軽量・高速 - 依存関係が少なく、ベンチマークでも高いスループットを記録
インストール
# npm
npm install postgres
# pnpm
pnpm add postgres
# yarn
yarn add postgres
# bun
bun add postgres
TypeScriptの型定義もパッケージに同梱されているため、追加の@typesパッケージは不要です。
基本的な使い方
まずは接続用のインスタンスを作成し、SELECTとINSERTを実行してみます。
// db.js
import postgres from 'postgres'
// 接続文字列、またはオプションオブジェクトで指定
// 未指定の項目はpsqlと同じ環境変数(PGHOST, PGUSERなど)にフォールバックする
const sql = postgres({
host: 'localhost',
port: 5432,
database: 'myapp',
username: 'postgres',
password: 'password',
})
export default sql
// users.js
import sql from './db.js'
// SELECT: ${age} は自動的にプレースホルダ($1)に変換される
async function getUsersOver(age) {
const users = await sql`
select name, age
from users
where age > ${age}
`
// users = [{ name: 'Walter', age: 80 }, { name: 'Murray', age: 68 }, ...]
return users
}
// INSERT: returning句で挿入結果を受け取れる
async function insertUser({ name, age }) {
const [user] = await sql`
insert into users (name, age)
values (${name}, ${age})
returning name, age
`
// user = { name: 'Murray', age: 68 }
return user
}
クエリはawaitした瞬間に初めて実行される点に注意してください。テンプレートリテラルを組み立てただけでは何も送信されません。
Postgres.jsはブラウザではなくNode.js/Deno/Bunのようなサーバーサイド環境でPostgreSQLへTCP接続するためのライブラリです。動作にはPostgreSQLサーバーへの実接続が必要なため、この記事ではブラウザ上で動く実行サンプル(LiveCodes)ではなく、実際のプロジェクトにそのまま貼り付けられるコード例で紹介します。
実践的なユースケース
動的なINSERT・UPDATE
オブジェクトのキーをそのままカラムとして扱いたい場面は多いですよね。sql(obj, ...columns)を使うと、フォームの入力値などをそのまま安全にクエリへ変換できます。
const user = {
name: 'Murray',
age: 68,
}
// insert into users ("name", "age") values ($1, $2)
await sql`
insert into users ${sql(user, 'name', 'age')}
`
// 複数行を一括INSERTする場合も配列を渡すだけでよい
const users = [
{ name: 'Murray', age: 68 },
{ name: 'Walter', age: 80 },
]
// insert into users ("name", "age") values ($1, $2), ($3, $4)
await sql`insert into users ${sql(users, 'name', 'age')}`
// UPDATEでも同じヘルパーが使える
await sql`
update users set ${sql(user, 'name', 'age')}
where user_id = ${1}
`
列名の許可リストを明示できるので、ユーザー入力をそのままカラム名に使ってしまう事故も防げます。
トランザクション処理
複数のテーブルにまたがる更新を、失敗時には自動でロールバックしたいケースです。sql.beginにコールバックを渡すだけで、専用の接続が確保され、例外発生時は自動的にROLLBACKされます。
async function createUserWithAccount(name) {
const [user, account] = await sql.begin(async (sql) => {
const [user] = await sql`
insert into users (name)
values (${name})
returning *
`
const [account] = await sql`
insert into accounts (user_id)
values (${user.user_id})
returning *
`
return [user, account]
})
return { user, account }
}
コールバック内で例外がスローされれば自動的にROLLBACKが呼ばれ、接続もプールへ返却されます。try/catchでエラー処理を書くだけで、トランザクション管理のボイラープレートを書かずに済みます。
LISTEN / NOTIFYでリアルタイム通知を受け取る
PostgreSQLのLISTEN/NOTIFY機能を使うと、他のクライアントからの通知をリアルタイムで受け取れます。ジョブキューの完了通知や、簡易的なPub/Subに便利な仕組みです。
// 通知を受け取る側
await sql.listen('news', (payload) => {
console.log('受信したペイロード:', payload)
})
// 別のプロセス・接続から通知を送る側
await sql.notify('news', JSON.stringify({ event: 'user_created', id: 42 }))
Redisなどの外部ミドルウェアを追加せずに、PostgreSQLだけで軽量な通知の仕組みを組めるのが魅力です。
まとめ
Postgres.jsは、ORMを介さずに素のSQLを書きたい人にとって、次のような価値を提供してくれます。
- 安全性: タグ付きテンプレートリテラルによって、意識せずともSQLインジェクション対策ができる
- 柔軟性:
sql()ヘルパーでカラムや値を動的に組み立てられる - シンプルさ: トランザクションやLISTEN/NOTIFYといった機能も最小限のコードで書ける
- マルチランタイム対応: Node.jsだけでなくDeno、Bun、Cloudflare Workersでも利用可能
DrizzleなどのORMも内部でPostgres.jsをドライバーとして採用しているケースがあるので、「ORMを使わずに素のSQLで組みたいプロジェクト」だけでなく、「ORMのドライバーとして何が動いているか知りたい」という人にも一度触ってみる価値があるライブラリです。