How to Set Up PostgreSQL with Prisma ORM in a Next.js Project

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
database code laptop

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.prisma for your data model
  • .env with a placeholder DATABASE_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
database code laptop

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 createdAt and updatedAt fields, you will thank yourself later
  • Add explicit indexes with @@index on fields you query often
  • Use @unique constraints 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:

  1. Creates a new SQL migration file in prisma/migrations/
  2. Applies it to your database
  3. 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);
}
database code laptop

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 generate to your build step, either in postinstall or in your build command
  • Run npx prisma migrate deploy as part of your deployment pipeline, never migrate dev in production
  • Set DATABASE_URL as 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.

Leave a Comment