Skip to content

Node.js basics

@pgmem/core starts the pgmem binary and hands you a PostgreSQL URL. The binary comes from a platform package that npm installs as an optional dependency; there is no postinstall script and nothing is downloaded at run time. pgmem does not replace your PostgreSQL client or ORM: Prisma, Drizzle, TypeORM, Kysely, pg and postgres connect to the URL as they would to any server, pools included.

Install pgmem together with the official client you use.

Terminal window
npm install --save-dev @pgmem/core pg

pg is node-postgres. postgres.js, Prisma, Drizzle and TypeORM work the same way; install whichever your application already uses. Node.js 20 or newer. Binary packages exist for Linux and macOS on x64 and arm64 and for Windows on x64 and arm64; elsewhere, point PGMEM_BINARY at a build of cmd/pgmem.

  1. Start a server. PgmemServer.start() spawns the binary, runs prepare against the template database, and snapshots the result so forks start from it.

    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=disable

    await using closes the server at the end of the scope; call server.close() yourself where explicit resource management is not available. Other options are user, params (postgres settings as an object, such as { log_statement: 'all' }), maxForks, waitTimeoutMs, log and binary.

  2. Register the schema in prepare. It receives the template server (url, host, port, user, database). See migration tools.

  3. Load seed data, also in prepare. See seed data. Close or commit every connection prepare opens: the snapshot waits for open transactions.

  4. Use it with the official clients.

    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();

    Keep sslmode=disable in the URL. pg treats prefer and require as “TLS required”, and pgmem has no TLS.

Every tool below reads a URL. Run it inside prepare, so it runs once on the template.

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 sends a query without parameters as one simple query, so several statements are fine.

Authoring migrations with prisma migrate dev

Section titled “Authoring migrations with prisma migrate dev”

prisma migrate dev needs a shadow database. pgmem serves every database a server has, so Prisma creates the shadow database on the same in-memory server, and you can author migrations without a local PostgreSQL:

scripts/migrate-dev.mjs
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();
}
Terminal window
node scripts/migrate-dev.mjs --name add_posts
await client.query(await readFile('seed.sql', 'utf8'));
await using fork = await server.fork(); // a private copy of the prepared database
await useDatabase(fork.url);
// or let pgmem close it
await server.withFork(async (fork) => {
await useDatabase(fork.url);
});
// put a fork back to the snapshot without changing its URL
await fork.reset();

A fork waits for a free slot while maxForks forks are alive. Closing a fork leaves idle pooled connections to their pools, so a pool without an error listener does not crash the process. The next page shows how the test runners use this.