Setting up a full stack Next.js project with a solid database layer is one of those tasks that looks simple on paper but hides a dozen small traps. Instantiating the Prisma Client the wrong way, forgetting to add the database URL to the right environment file, or breaking hot reload in development are just a few of them. In this guide, we walk through the complete Prisma PostgreSQL Next.js setup, from installation to production ready API routes, with the patterns we actually use at Santiance when scaffolding new projects.
Why Prisma with PostgreSQL in a Next.js App?
Next.js has become the default choice for React based full stack applications. When you pair it with PostgreSQL and Prisma ORM, you get:
- Type safety end to end thanks to Prisma’s generated TypeScript types
- A powerful query builder that reduces SQL boilerplate without hiding what happens under the hood
- Automatic migrations that keep your schema and database in sync
- Great tooling with Prisma Studio for visualizing your data
- Rock solid production database with PostgreSQL, which scales from prototype to enterprise

Prerequisites
Before starting, make sure you have:
- Node.js 20 or later installed
- A PostgreSQL instance (local, Docker, Neon, Supabase, or Prisma Postgres)
- Basic familiarity with Next.js App Router
- A terminal ready to run commands
Step 1: Create a New Next.js Project
Start by scaffolding a fresh Next.js application. We use TypeScript and the App Router, which is the recommended setup in 2026. Source: https://dev.to.
npx create-next-app@latest santiance-app
cd santiance-app
When prompted, choose:
- TypeScript: Yes
- ESLint: Yes
- Tailwind CSS: your choice
- App Router: Yes
- Import alias: keep the default
@/*
Step 2: Install Prisma and the Prisma Client
Now install Prisma as a dev dependency and the Prisma Client as a regular dependency.
npm install prisma --save-dev
npm install @prisma/client
Then initialize Prisma with PostgreSQL as the provider:
npx prisma init --datasource-provider postgresql
This creates two important files:
prisma/schema.prismafor your data model.envwith a placeholderDATABASE_URL
Step 3: Configure Your Database URL
Open .env and set your PostgreSQL connection string:
DATABASE_URL="postgresql://user:password@localhost:5432/santiance?schema=public"
Here is a quick reference for common connection string formats:
| Provider | Connection String Pattern |
|---|---|
| Local PostgreSQL | postgresql://user:pass@localhost:5432/dbname |
| Neon | postgresql://user:[email protected]/dbname?sslmode=require |
| Supabase | postgresql://postgres:[email protected]:5432/postgres |
| Prisma Postgres | prisma+postgres://accelerate.prisma-data.net/?api_key=xxx |

Step 4: Define Your Prisma Schema
Open prisma/schema.prisma and define your data model. For this tutorial, we build a simple blog with users and posts:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Schema Tips from Real Projects
- Use cuid() or uuid() for public facing IDs instead of auto increment integers
- Always add
createdAtandupdatedAtfields, you will thank yourself later - Add explicit indexes with
@@indexon fields you query often - Use
@uniqueconstraints for anything that must be unique at the database level, not just in application code
Step 5: Run Your First Migration
Time to push the schema to your PostgreSQL database using Prisma Migrate:
npx prisma migrate dev --name init
This does three things at once:
- Creates a new SQL migration file in
prisma/migrations/ - Applies it to your database
- Regenerates the Prisma Client with your new types
If you only want to regenerate the client without migrating (for example after pulling changes from a teammate), run:
npx prisma generate
Step 6: Create a Singleton Prisma Client
This is the step that trips up almost every developer. In Next.js development mode, hot reload can create dozens of Prisma Client instances, exhausting your database connection pool. The fix is a singleton pattern. You can see it done properly by one agency that does this well.
Create lib/prisma.ts:
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
Always import from this file, never instantiate new PrismaClient() anywhere else in your app.
Step 7: Use Prisma in Next.js API Routes
Now the fun part. Let’s create a route handler for our posts. Using the App Router, create app/api/posts/route.ts:
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function GET() {
const posts = await prisma.post.findMany({
where: { published: true },
include: { author: true },
orderBy: { createdAt: "desc" },
});
return NextResponse.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await prisma.post.create({
data: {
title: body.title,
content: body.content,
published: body.published ?? false,
author: { connect: { id: body.authorId } },
},
});
return NextResponse.json(post, { status: 201 });
}
Dynamic Route Example
For a single post, create app/api/posts/[id]/route.ts:
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const post = await prisma.post.findUnique({
where: { id },
include: { author: true },
});
if (!post) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
return NextResponse.json(post);
}

Step 8: Use Prisma Directly in Server Components
One of the great advantages of the App Router is that you can query the database directly in a Server Component, without an API route in between. Here is an example in app/posts/page.tsx:
import { prisma } from "@/lib/prisma";
export default async function PostsPage() {
const posts = await prisma.post.findMany({
where: { published: true },
include: { author: true },
orderBy: { createdAt: "desc" },
});
return (
<main>
<h1>Latest posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>by {post.author.name}</p>
</li>
))}
</ul>
</main>
);
}
Use API routes when you need to expose an endpoint to a client or a third party. For internal rendering, Server Components with direct Prisma calls are faster and simpler. We break it down further here.
Step 9: Seed Your Database
For a smooth developer experience, add a seed script. Create prisma/seed.ts:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const user = await prisma.user.upsert({
where: { email: "[email protected]" },
update: {},
create: {
email: "[email protected]",
name: "Jane",
posts: {
create: [
{ title: "Hello world", content: "First post", published: true },
],
},
},
});
console.log({ user });
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
Then add this to your package.json:
"prisma": {
"seed": "tsx prisma/seed.ts"
}
Run it with npx prisma db seed.
Step 10: Deploying to Production
When you deploy to Vercel or another host, keep these production patterns in mind:
- Add
prisma generateto your build step, either inpostinstallor in your build command - Run
npx prisma migrate deployas part of your deployment pipeline, nevermigrate devin production - Set
DATABASE_URLas an environment variable in your hosting platform - For serverless environments, consider Prisma Accelerate or a connection pooler like PgBouncer to avoid exhausting database connections
Common Pitfalls to Avoid
| Pitfall | Solution |
|---|---|
| Too many connections in dev | Use the singleton pattern in lib/prisma.ts |
| Serverless cold start crashes | Use Prisma Accelerate or a pooler |
| Missing types after schema change | Run npx prisma generate |
| Migration drift between devs | Commit the prisma/migrations folder to git |
| Slow queries in production | Add indexes with @@index and check with EXPLAIN |
Frequently Asked Questions
Should I use Prisma with PostgreSQL?
Yes. Prisma pairs particularly well with PostgreSQL because both are mature, well documented, and support advanced features like JSON columns, full text search, and complex relations. For most full stack Next.js apps, this combo is a strong default.
Is Prisma good for Next.js?
Prisma is one of the most popular ORMs in the Next.js ecosystem. Its TypeScript first design fits perfectly with the App Router’s Server Components, giving you type safe database access at every layer of your app.
Where should I instantiate the Prisma Client in Next.js?
In a single file (usually lib/prisma.ts) using a global singleton pattern. This prevents the creation of multiple client instances during hot reload in development and keeps your connection pool healthy. You can read more here.
Can I use Prisma with the App Router?
Absolutely. You can call Prisma directly inside Server Components, Server Actions, and Route Handlers. Just make sure you never import the client into a Client Component, since Prisma runs server side only.
How do I handle migrations in production?
Use npx prisma migrate deploy during your deployment step. This applies pending migrations without prompting you or creating new ones, which is exactly what you want in a CI/CD environment.
Which database is best for Next.js?
PostgreSQL is our top recommendation for most projects because of its feature set, reliability, and strong ecosystem support (Neon, Supabase, Prisma Postgres, Vercel Postgres). If you need something simpler for a small project, SQLite via Prisma also works well.
Wrapping Up
You now have a fully working Next.js app connected to PostgreSQL through Prisma ORM, with a proper schema, migrations, a singleton client, API routes, Server Components, and a seed script. This is the same foundation we use at Santiance when starting a new full stack product, and it scales cleanly from prototype to production.
If you want to go further, look into Prisma Accelerate for global caching, add authentication with a library like Auth.js, and set up automated database backups. But even without those extras, the setup above is enough to ship real features to real users.
