Node.js の基本
@pgmem/core は pgmem バイナリを起動し、PostgreSQL の URL を渡してくれます。バイナリは npm がオプション依存としてインストールするプラットフォーム別パッケージに入っています。postinstall スクリプトはなく、実行時のダウンロードもありません。pgmem は PostgreSQL クライアントや ORM を置き換えるものではありません。Prisma、Drizzle、TypeORM、Kysely、pg、postgres は、ほかのサーバーと同じように URL へ接続するだけで、プールもそのまま使えます。
インストール
Section titled “インストール”pgmem と、使っている公式クライアントを一緒にインストールします。
npm install --save-dev @pgmem/core pgpnpm add --save-dev @pgmem/core pgyarn add --dev @pgmem/core pgbun add --dev @pgmem/core pgpg は node-postgres です。postgres.js、Prisma、Drizzle、TypeORM も同じように使えるので、アプリケーションで使っているものを入れてください。Node.js 20 以降が必要です。バイナリのパッケージは Linux と macOS の x64・arm64、Windows の x64・arm64 向けにあります。それ以外の環境では PGMEM_BINARY で cmd/pgmem のビルドを指定してください。
-
サーバーを起動します。
PgmemServer.start()はバイナリを起動し、テンプレートのデータベースに対してprepareを実行し、その結果をスナップショットにします。フォークはそこから始まります。import { PgmemServer } from '@pgmem/core';await using server = await PgmemServer.start({database: 'app',async prepare({ url }) {await migrate(url);await seed(url);},});console.log(server.url); // postgres://postgres@127.0.0.1:54321/app?sslmode=disableawait usingはスコープの終わりでサーバーを閉じます。明示的なリソース管理が使えない環境ではserver.close()を自分で呼んでください。ほかのオプションはuser、params({ log_statement: 'all' }のような postgres の設定のオブジェクト)、maxForks、waitTimeoutMs、log、binaryです。 -
スキーマを登録します。
prepareの中で行います。prepareにはテンプレートのサーバー(url、host、port、user、database)が渡されます。マイグレーションツールを参照してください。 -
シードデータを入れます。 これも
prepareの中です。シードデータを参照してください。スナップショットは開いているトランザクションを待つので、prepareで開いた接続はすべて閉じるかコミットしてください。 -
公式クライアントから使います。
import pg from 'pg';const pool = new pg.Pool({ connectionString: server.url });const { rows } = await pool.query('SELECT name FROM users WHERE id = $1', [1]);await pool.end();import postgres from 'postgres';const sql = postgres(server.url);const [user] = await sql`SELECT name FROM users WHERE id = ${1}`;await sql.end();import { drizzle } from 'drizzle-orm/node-postgres';const db = drizzle(server.url);const rows = await db.select().from(users);import { PrismaPg } from '@prisma/adapter-pg';import { PrismaClient } from '../generated/prisma/client';const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: server.url }) });const user = await prisma.user.findUnique({ where: { id: 1 } });URL の
sslmode=disableは残してください。pgはpreferとrequireを「TLS 必須」と扱いますが、pgmem には TLS がありません。
マイグレーションツール
Section titled “マイグレーションツール”どのツールも URL を受け取ります。prepare の中で実行すれば、テンプレートに対して一度だけ走ります。
import { readFile } from 'node:fs/promises';import pg from 'pg';
async function migrate(url: string) { const client = new pg.Client({ connectionString: url }); await client.connect(); await client.query(await readFile('schema.sql', 'utf8')); await client.end();}node-postgres はパラメータのないクエリを 1 回のシンプルクエリとして送るので、複数の文を書いてかまいません。
import { execFileSync } from 'node:child_process';
function migrate(url: string) { execFileSync('npx', ['prisma', 'migrate', 'deploy'], { stdio: 'inherit', env: { ...process.env, DATABASE_URL: url }, shell: process.platform === 'win32', });}prisma.config.ts では環境変数から URL を読みます:datasource: { url: process.env.DATABASE_URL ?? '' }。prisma db push も同じように使えます。
import { drizzle } from 'drizzle-orm/node-postgres';import { migrate as drizzleMigrate } from 'drizzle-orm/node-postgres/migrator';
async function migrate(url: string) { const db = drizzle(url); await drizzleMigrate(db, { migrationsFolder: 'drizzle' }); await db.$client.end();}import { DataSource } from 'typeorm';
async function migrate(url: string) { const ds = await new DataSource({ ...options, url }).initialize(); await ds.runMigrations(); await ds.destroy();}import knex from 'knex';
async function migrate(url: string) { const db = knex({ client: 'pg', connection: url }); await db.migrate.latest(); await db.destroy();}prisma migrate dev でマイグレーションを作る
Section titled “prisma migrate dev でマイグレーションを作る”prisma migrate dev にはシャドウデータベースが必要です。pgmem のサーバーは持っているすべてのデータベースに接続できるので、Prisma は同じメモリ上のサーバーにシャドウデータベースを作ります。ローカルに PostgreSQL がなくてもマイグレーションを作れます。
import { spawnSync } from 'node:child_process';import { PgmemServer } from '@pgmem/core';
const server = await PgmemServer.start({ database: 'app', control: false });try { const run = spawnSync('npx', ['prisma', 'migrate', 'dev', ...process.argv.slice(2)], { stdio: 'inherit', env: { ...process.env, DATABASE_URL: server.url }, shell: process.platform === 'win32', }); process.exitCode = run.status ?? 1;} finally { await server.close();}node scripts/migrate-dev.mjs --name add_postsシードデータ
Section titled “シードデータ”await client.query(await readFile('seed.sql', 'utf8'));execFileSync('npx', ['prisma', 'db', 'seed'], { stdio: 'inherit', env: { ...process.env, DATABASE_URL: url }, shell: process.platform === 'win32',});const db = drizzle(url);await db.insert(users).values([ { id: 1, name: 'Frank', email: 'frank@example.com' }, { id: 2, name: 'Grace', email: 'grace@example.com' },]);await db.$client.end();フォークを直接使う
Section titled “フォークを直接使う”await using fork = await server.fork(); // 準備済みデータベースの専用の複製await useDatabase(fork.url);
// pgmem に閉じてもらう書き方await server.withFork(async (fork) => { await useDatabase(fork.url);});
// URL を変えずにフォークをスナップショットの状態に戻すawait fork.reset();生きているフォークが maxForks 個あると、次のフォークは空きが出るまで待ちます。フォークを閉じても、アイドル状態のプール接続はプールに任されます。error リスナーのないプールでもプロセスが落ちることはありません。次のページでは、テストランナーからの使い方を紹介します。