Use Prisma Postgres with Bun

Prisma Postgres is a managed PostgreSQL database. In this guide you scaffold a Bun project with Prisma ORM 8, create a Prisma Postgres database, run a typed query against it, serve the results with Bun.serve, and deploy the server to Prisma Compute.

Create a new project

Scaffold the project with create-prisma, whose minimal template sets up Prisma ORM 8 in src/prisma/ and installs the dependencies with Bun. The --authoring psl flag writes the schema as src/prisma/contract.prisma, the Prisma Schema Language file this guide edits, and --yes answers the only remaining prompt, which asks which coding agents to install skills for, with its default of all of them. Leave --yes out if you want to pick the agents yourself.

terminal
bun create prisma@latest prisma-postgres-app --template minimal --provider postgres --authoring psl --package-manager bun --no-deploy --yes
cd prisma-postgres-app

Create a Prisma Postgres database

Create a database with create-db, which needs no account and prints a postgres:// connection string together with a claim URL. An unclaimed database is deleted after 24 hours, so open that URL if you want to keep it. The connection string of any other empty PostgreSQL 15 or newer database works too.

terminal
bunx create-db

Put the connection string in a .env file. The string from create-db ends in sslmode=require, which makes the pg driver print a security warning on every connection, so change it to sslmode=verify-full, the mode Prisma Postgres supports. Leave the SSL settings of another provider as its documentation gives them.

.env
DATABASE_URL="postgres://USER:PASSWORD@db.prisma.io:5432/postgres?sslmode=verify-full"

Initialize the database

Prisma ORM 8 compiles the schema in src/prisma/contract.prisma into a contract that your queries are type-checked against, so emit it first and then create the tables. The second command needs the connection string, and because Bun loads .env when it runs a file but not when it runs a package.json script, pass the file with --env-file.

terminal
bun run contract:emit
bun run --env-file=.env db:init
✔ Applied 5 operation(s) across 1 contract space

App space
├─ Create table "Post"
├─ Create table "User"
├─ Add unique constraint on "User" (email)
├─ Create index "Post_authorId_idx_e47547ed" on "Post"
├─ Add foreign key "Post_authorId_fkey" on "Post"
└─ marker a51b4833fdaffe730572670ccc27dd89af7e0726ab3de7f428d21702d687cc15

If db:init stops with Connection terminated unexpectedly, the new database is still starting, so wait a few seconds and run the command again, which is safe to repeat.

Create a test script

Replace src/index.ts with a script that creates a new user, then counts the users in the database. Models are namespace-qualified on PostgreSQL, so the User model in the default public schema is db.orm.public.User.

src/index.ts
import { db } from "./prisma/db";

// create a new user
await db.orm.public.User.create({
  name: "John Dough",
  email: `john-${Math.random()}@example.com`,
});

// count the users
const { count } = await db.orm.public.User.aggregate(a => ({ count: a.count() }));
console.log(`There are ${count} users in the database.`);

await db.close();

The final db.close() lets the script exit, because otherwise the connection pool keeps the process alive.

Run and test the application

Run the script with bun, which loads .env on its own, and watch the count grow because each run creates a new user.

terminal
bun src/index.ts
There are 1 users in the database.
terminal
bun src/index.ts
There are 2 users in the database.
terminal
bun src/index.ts
There are 3 users in the database.

Serve users over HTTP

Create src/server.ts with a Bun.serve server that returns the users as JSON. Unlike the script, the server shares one connection pool across requests, so it never calls db.close().

src/server.ts
import { db } from "./prisma/db";

const server = Bun.serve({
  port: Number(process.env.PORT ?? 3000),
  async fetch(req) {
    const { pathname } = new URL(req.url);
    if (pathname === "/users") {
      const users = await db.orm.public.User.select("id", "email", "name").all();
      return Response.json(users);
    }
    return new Response("Not found", { status: 404 });
  },
});

console.log(`Listening on http://localhost:${server.port}`);

Because the scaffold ships Node.js types only, install Bun's types and add "bun" to the types array in tsconfig.json so that Bun.serve type-checks:

terminal
bun add -d @types/bun
tsconfig.json
{
  "compilerOptions": {
    "types": ["node"] 
    "types": ["node", "bun"] 
  }
}

Start the server and request the users:

terminal
bun src/server.ts
terminal
curl http://localhost:3000/users
[
  { "id": 1, "email": "john-0.5967@example.com", "name": "John Dough" },
  { "id": 2, "email": "john-0.5315@example.com", "name": "John Dough" },
  { "id": 3, "email": "john-0.5720@example.com", "name": "John Dough" }
]

Deploy to Prisma Compute

Prisma Compute runs the server on Bun next to a Prisma Postgres database that it provisions for the app. The scaffold already declares both in module.ts and service.ts, and its deploy script bundles the entry in tsdown.config.ts with tsdown before deploying. Point that entry at the server:

tsdown.config.ts
export default defineConfig({
  entry: { server: "src/index.ts" }, 
  entry: { server: "src/server.ts" }, 
  // the rest of the generated config is unchanged
});

Sign in once, which opens a browser, then build and deploy:

terminal
bunx prisma auth login
bun run deploy
prisma-postgres-app
├─ database   postgres-database db_abc123
└─ app        compute-service cps_abc123
              https://abc123.ewr.prisma.build

The deployed app uses the database that the deploy created, not the one from .env, so the live endpoint starts with an empty list:

terminal
curl https://abc123.ewr.prisma.build/users
[]

Prisma Postgres is now set up with Bun. Refer to the Prisma Postgres docs as you continue to develop your application, and to Deploy on push to deploy from a GitHub repository instead of your terminal.