apps/docs/content/docs.v6/postgres/database/serverless-driver.mdx
The Prisma Postgres serverless driver (@prisma/ppg) is a lightweight client for connecting to Prisma Postgres using raw SQL. It uses HTTP and WebSocket protocols instead of traditional TCP connections, enabling database access in constrained environments where native PostgreSQL drivers cannot run.
:::warning
The Prisma Postgres serverless driver is currently in Early Access and not yet recommended for production scenarios.
:::
The serverless driver uses HTTP and WebSocket protocols instead of TCP, enabling database access in environments where traditional PostgreSQL drivers cannot run:
Use this driver for edge/serverless environments without full Node.js support, or when working with large result sets that benefit from streaming.
For standard Node.js environments, use the node-postgres driver for lower latency with direct TCP connections.
The serverless driver requires a Prisma Postgres Direct TCP connection URL:
postgres://identifier:[email protected]:5432/postgres?sslmode=require
Find this in the API Keys section of your Prisma Postgres dashboard. The connection string is used only to extract authentication credentials. No direct TCP connection is made from the client.
If you don't have a Prisma Postgres database, create one using the create-db CLI tool:
npx prisma create-db
Install the appropriate package based on your use case:
npm install @prisma/ppg
npm install @prisma/ppg @prisma/adapter-ppg
Use the prismaPostgres() high-level API with SQL template literals and automatic parameterization:
import { prismaPostgres, defaultClientConfig } from "@prisma/ppg";
const ppg = prismaPostgres(defaultClientConfig(process.env.PRISMA_DIRECT_TCP_URL!));
type User = { id: number; name: string; email: string };
// SQL template literals with automatic parameterization
const users = await ppg.sql<User>`
SELECT * FROM users WHERE email = ${"[email protected]"}
`.collect();
console.log(users[0].name);
Use the PrismaPostgresAdapter to connect Prisma Client via the serverless driver:
import { PrismaClient } from "../generated/prisma/client";
import { PrismaPostgresAdapter } from "@prisma/adapter-ppg";
const prisma = new PrismaClient({
adapter: new PrismaPostgresAdapter({
connectionString: process.env.PRISMA_DIRECT_TCP_URL,
}),
});
const users = await prisma.user.findMany();
Results are returned as CollectableIterator<T>. Stream rows one at a time for constant memory usage, or collect all rows into an array:
type User = { id: number; name: string; email: string };
// Stream rows one at a time (constant memory usage)
for await (const user of ppg.sql<User>`SELECT * FROM users`) {
console.log(user.name);
}
// Or collect all rows into an array
const allUsers = await ppg.sql<User>`SELECT * FROM users`.collect();
Send multiple queries over a single WebSocket connection without waiting for responses. Queries are sent immediately and results arrive in FIFO order:
import { client, defaultClientConfig } from "@prisma/ppg";
const cl = client(defaultClientConfig(process.env.PRISMA_DIRECT_TCP_URL!));
const session = await cl.newSession();
// Send all queries immediately (pipelined)
const [usersResult, ordersResult, productsResult] = await Promise.all([
session.query("SELECT * FROM users"),
session.query("SELECT * FROM orders"),
session.query("SELECT * FROM products"),
]);
session.close();
With 100ms network latency, 3 sequential queries take 300ms (3 x RTT), but pipelined queries take only 100ms (1 x RTT).
Parameters over 1KB are automatically streamed without buffering in memory. For large binary parameters, you must use boundedByteStreamParameter() which creates a BoundedByteStreamParameter object that carries the total byte size, required by the PostgreSQL protocol:
import { client, defaultClientConfig, boundedByteStreamParameter, BINARY } from "@prisma/ppg";
const cl = client(defaultClientConfig(process.env.PRISMA_DIRECT_TCP_URL!));
// Large binary data (e.g., file content)
const stream = getReadableStream(); // Your ReadableStream source
const totalSize = 1024 * 1024; // Total size must be known in advance
// Create a bounded byte stream parameter
const streamParam = boundedByteStreamParameter(stream, BINARY, totalSize);
// Automatically streamed - constant memory usage
await cl.query("INSERT INTO files (data) VALUES ($1)", streamParam);
For Uint8Array data, use byteArrayParameter():
import { client, defaultClientConfig, byteArrayParameter, BINARY } from "@prisma/ppg";
const cl = client(defaultClientConfig(process.env.PRISMA_DIRECT_TCP_URL!));
const bytes = new Uint8Array([1, 2, 3, 4]);
const param = byteArrayParameter(bytes, BINARY);
await cl.query("INSERT INTO files (data) VALUES ($1)", param);
The boundedByteStreamParameter() function is provided by the @prisma/ppg library and requires the total byte size to be known in advance due to PostgreSQL protocol requirements.
Transactions automatically handle BEGIN, COMMIT, and ROLLBACK:
const result = await ppg.transaction(async (tx) => {
await tx.sql.exec`INSERT INTO users (name) VALUES ('Alice')`;
const users = await tx.sql<User>`SELECT * FROM users WHERE name = 'Alice'`.collect();
return users[0].name;
});
Batch operations execute multiple statements in a single round-trip within an automatic transaction:
const [users, affected] = await ppg.batch<[User[], number]>(
{ query: "SELECT * FROM users WHERE id < $1", parameters: [5] },
{ exec: "INSERT INTO users (name) VALUES ($1)", parameters: ["Charlie"] },
);
When using defaultClientConfig(), common PostgreSQL types are automatically parsed (boolean, int2, int4, int8, float4, float8, text, varchar, json, jsonb, date, timestamp, timestamptz):
import { prismaPostgres, defaultClientConfig } from "@prisma/ppg";
const ppg = prismaPostgres(defaultClientConfig(process.env.PRISMA_DIRECT_TCP_URL!));
// JSON/JSONB automatically parsed
const rows = await ppg.sql<{ data: { key: string } }>`
SELECT '{"key": "value"}'::jsonb as data
`.collect();
console.log(rows[0].data.key); // "value"
// BigInt parsed to JavaScript BigInt
const bigints = await ppg.sql<{
big: bigint;
}>`SELECT 9007199254740991::int8 as big`.collect();
// Dates parsed to Date objects
const dates = await ppg.sql<{
created: Date;
}>`SELECT NOW() as created`.collect();
Extend or override the type system with custom parsers (by PostgreSQL OID) and serializers (by type guard):
import { client, defaultClientConfig } from "@prisma/ppg";
import type { ValueParser } from "@prisma/ppg";
// Custom parser for UUID type
const uuidParser: ValueParser<string> = {
oid: 2950,
parse: (value) => (value ? value.toUpperCase() : null),
};
const config = defaultClientConfig(process.env.PRISMA_DIRECT_TCP_URL!);
const cl = client({
...config,
parsers: [...config.parsers, uuidParser], // Append to defaults
});
For custom serializers, place them before defaults so they take precedence:
import type { ValueSerializer } from "@prisma/ppg";
class Point {
constructor(
public x: number,
public y: number,
) {}
}
const pointSerializer: ValueSerializer<Point> = {
supports: (value: unknown): value is Point => value instanceof Point,
serialize: (value: Point) => `(${value.x},${value.y})`,
};
const config = defaultClientConfig(process.env.PRISMA_DIRECT_TCP_URL!);
const cl = client({
...config,
serializers: [pointSerializer, ...config.serializers], // Your serializer first
});
await cl.query("INSERT INTO locations (point) VALUES ($1)", new Point(10, 20));
See the npm package documentation for more details.
The driver works in any environment with fetch and WebSocket APIs:
| Platform | HTTP Transport | WebSocket Transport |
|---|---|---|
| Cloudflare Workers | ✅ | ✅ |
| Vercel Edge Functions | ✅ | ✅ |
| AWS Lambda | ✅ | ✅ |
| Deno Deploy | ✅ | ✅ |
| Bun | ✅ | ✅ |
| Node.js 18+ | ✅ | ✅ |
| Browsers | ✅ | ✅ (with CORS) |
client().newSession().prismaPostgres(config)High-level API with SQL template literals, transactions, and batch operations. Recommended for most use cases.
client(config)Low-level API with explicit parameter passing and session management. Use when you need fine-grained control.
See the npm package for complete API documentation.
Structured error types are provided: DatabaseError, HttpResponseError, WebSocketError, ValidationError.
import { DatabaseError } from "@prisma/ppg";
try {
await ppg.sql`SELECT * FROM invalid_table`.collect();
} catch (error) {
if (error instanceof DatabaseError) {
console.log(error.code);
}
}
The serverless driver automatically uses connection pooling across all available Prisma Postgres regions for optimal performance and resource utilization.
Connection pooling is enabled by default and requires no additional configuration.
This ensures efficient database connections regardless of your deployment region, reducing connection overhead and improving query performance.