Introduction
This guide walks through building a Next.js 15 app with Prisma Postgres as the database. Prisma Postgres is a fully managed, serverless Postgres that integrates directly with the Prisma ORM. No separate hosting or setup needed.
You’ll connect Next.js to Prisma Postgres, run migrations, seed data, query with Prisma Client, and deploy to Vercel.
Full example here: GitHub repo.
Prerequisites
- Node.js 18+
- A Vercel account (for deployment)
1) Create the app
First, scaffold a new Next.js 15 project.
npx create-next-app@latest nextjs-prisma
cd nextjs-prisma
This creates a new project folder with Next.js defaults. Choose TypeScript, ESLint, Tailwind, and the App Router.
2) Install Prisma and init Prisma Postgres
Next, install Prisma and set it up with Prisma Postgres.
npm i -D prisma tsx
npm i @prisma/client @prisma/extension-accelerate
npx prisma init --db --output ../app/generated/prisma
During init, you’ll be asked to create a Prisma Postgres database. Pick a region close to your users and give it a name.
This step gives you:
- a
prisma/schema.prisma
file - a Prisma Postgres database provisioned automatically
-
.env
withDATABASE_URL
prefilled - a generated Prisma Client in
app/generated/prisma
If you’re using your own Postgres database, you can skip @prisma/extension-accelerate and provide your own DATABASE_URL.
3) Define the schema
Now define your models in the Prisma schema.
generator client {
provider = "prisma-client"
output = "../app/generated/prisma"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
}
This creates a User
model with a one-to-many relationship to Post
.
Apply the schema to your Prisma Postgres database:
npx prisma migrate dev --name init
This runs a migration and creates the corresponding tables inside Prisma Postgres.
4) Seed data
To test your app, add some initial users and posts. Create prisma/seed.ts
:
import { PrismaClient } from "../app/generated/prisma/client";
const prisma = new PrismaClient();
async function main() {
await prisma.user.create({
data: {
name: "Alice",
email: "alice@prisma.io",
posts: {
create: [
{ title: "Join the Prisma Discord", content: "<https://pris.ly/discord>", published: true },
{ title: "Prisma on YouTube", content: "<https://pris.ly/youtube>" }
]
}
}
});
await prisma.user.create({
data: {
name: "Bob",
email: "bob@prisma.io",
posts: {
create: [{ title: "Follow Prisma on Twitter", content: "<https://twitter.com/prisma>", published: true }]
}
}
});
}
main();
Tell Prisma how to run the seed script by adding to package.json
:
{
"prisma": { "seed": "tsx prisma/seed.ts" }
}
Run the seed and open Prisma Studio to view data in Prisma Postgres:
npx prisma db seed
npx prisma studio
5) Create a single Prisma Client
Next.js hot reload can create multiple Prisma clients, which can exhaust connections. To prevent that, create a single shared client in lib/prisma.ts
:
import { PrismaClient } from "../app/generated/prisma/client";
import { withAccelerate } from "@prisma/extension-accelerate";
const globalForPrisma = global as unknown as { prisma?: PrismaClient };
const prisma =
globalForPrisma.prisma ??
new PrismaClient().$extends(withAccelerate());
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}
export default prisma;
This reuses the client in dev. In production, Prisma Postgres handles pooling and scaling automatically.
6) Read data in Server Components
With Prisma Client ready, query Prisma Postgres directly inside async Server Components.
Example home page showing all users:
// app/page.tsx
import prisma from "@/lib/prisma";
export default async function Home() {
const users = await prisma.user.findMany();
return (
<div className="min-h-screen flex flex-col items-center justify-center">
<h1 className="text-4xl font-bold mb-8">Superblog</h1>
<ol className="list-decimal">
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ol>
</div>
);
}
Posts page including authors:
// app/posts/page.tsx
import prisma from "@/lib/prisma";
export default async function Posts() {
const posts = await prisma.post.findMany({ include: { author: true } });
return (
<div className="min-h-screen flex flex-col items-center justify-center">
<h1 className="text-4xl font-bold mb-8">Posts</h1>
<ul className="space-y-4">
{posts.map(p => (
<li key={p.id}>
<strong>{p.title}</strong> by {p.author?.name ?? "Anonymous"}
</li>
))}
</ul>
</div>
);
}
Dynamic post detail route:
// app/posts/[id]/page.tsx
import prisma from "@/lib/prisma";
import { notFound } from "next/navigation";
export default async function Post({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const post = await prisma.post.findUnique({
where: { id: Number(id) },
include: { author: true }
});
if (!post) notFound();
return (
<article className="max-w-2xl mx-auto p-6">
<h1 className="text-3xl font-bold">{post.title}</h1>
<p className="text-gray-600">by {post.author?.name ?? "Anonymous"}</p>
<div className="mt-6">{post.content || "No content available."}</div>
</article>
);
}
7) Create data with Server Actions
Now add a form that writes to Prisma Postgres using a Server Action.
// app/posts/new/page.tsx
import Form from "next/form";
import prisma from "@/lib/prisma";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export default function NewPost() {
async function createPost(formData: FormData) {
"use server";
const title = String(formData.get("title") || "");
const content = String(formData.get("content") || "");
await prisma.post.create({ data: { title, content, authorId: 1 } });
revalidatePath("/posts");
redirect("/posts");
}
return (
<div className="max-w-2xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">Create New Post</h1>
<Form action={createPost} className="space-y-4">
<input name="title" placeholder="Title" className="w-full border px-3 py-2 rounded" />
<textarea name="content" placeholder="Content" rows={6} className="w-full border px-3 py-2 rounded" />
<button type="submit" className="w-full bg-blue-600 text-white py-2 rounded">Create Post</button>
</Form>
</div>
);
}
Server Actions run directly against Prisma Postgres, with no need for API routes.
8) Dev notes
- On Next.js 15.2.0 or 15.2.1, avoid Turbopack (bug). Change your
package.json
dev script to"dev": "next dev"
. - Prisma Studio is handy for quickly viewing your Prisma Postgres data.
9) Deploy to Vercel
Finally, deploy the app with Prisma Postgres as the backing DB.
npm i -g vercel
vercel login
Update package.json
so Prisma Client is generated on install:
{
"scripts": {
"postinstall": "prisma generate --no-engine"
}
}
If you’re not using Prisma Postgres, remove --no-engine.
Deploy:
vercel
Vercel builds and deploys your app, while Prisma Postgres provides the globally available database — no extra hosting needed.
Docs for further reading:
Top comments (0)