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.
bun create prisma@latest prisma-postgres-app --template minimal --provider postgres --authoring psl --package-manager bun --no-deploy --yes
cd prisma-postgres-appCreate 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.
bunx create-dbPut 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.
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.
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 a51b4833fdaffe730572670ccc27dd89af7e0726ab3de7f428d21702d687cc15If 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.
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.
bun src/index.tsThere are 1 users in the database.bun src/index.tsThere are 2 users in the database.bun src/index.tsThere 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().
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:
bun add -d @types/bun{
"compilerOptions": {
"types": ["node"]
"types": ["node", "bun"]
}
}Start the server and request the users:
bun src/server.tscurl 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:
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:
bunx prisma auth login
bun run deployprisma-postgres-app
├─ database postgres-database db_abc123
└─ app compute-service cps_abc123
https://abc123.ewr.prisma.buildThe deployed app uses the database that the deploy created, not the one from .env, so the live endpoint starts with an empty list:
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.