Prisma with Next.js integration guide

Setup Prisma with Next.js (App Router), connect your database, and use Prisma safely in API routes and server code.

Jul 8, 20268 min read

What Prisma is and why it fits Next.js

Prisma is an ORM that turns your database tables into a type-safe API (Prisma Client). With Next.js, Prisma helps you avoid handwritten SQL, keeps queries consistent, and provides good TypeScript support. In an App Router project, the main rule is simple:

  • Use Prisma only on the server.
  • Do not import Prisma Client into browser code.

Prerequisites

  • Node.js and pnpm (or npm)
  • A database you can connect to (PostgreSQL, MySQL, SQLite)
  • A Next.js app using the App Router

Step 1: Install Prisma

From the project root:

bash
pnpm add -D prisma
pnpm add @prisma/client

If you use npm, replace <code>pnpm</code> with <code>npm</code>.

Step 2: Initialize Prisma

bash
pnpm prisma init --datasource-provider postgresql

This creates:

  • <code>prisma/schema.prisma</code>
  • <code>.env</code> with a <code>DATABASE_URL</code>

If your database is not PostgreSQL, use the correct provider.

Step 3: Configure DATABASE_URL

In <code>.env</code>:

env
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DB_NAME?schema=public"

Never commit real credentials. Keep <code>.env</code> out of version control.

Step 4: Define your Prisma schema

Edit <code>prisma/schema.prisma</code>. Example schema:

prisma
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?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

Notes:

  • Use <code>cuid()</code> for short, collision resistant IDs.
  • Use <code>@unique</code> for fields like <code>email</code>.

Step 5: Create and apply migrations

bash
pnpm prisma migrate dev --name init

This:

  • generates a migration
  • applies it locally
  • updates Prisma Client

For production, you will run migrations differently (for example in CI or during deploy).

Step 6: Add a safe Prisma Client helper for Next.js

The App Router can rerender on every request in development. To avoid creating many Prisma Client instances, create a shared helper. Create <code>lib/prisma.ts</code>:

ts
import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as {
  prisma?: PrismaClient;
};

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    log: ["error", "warn"],
  });

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

Guidelines:

  • This file is server-only by convention.
  • Only import <code>prisma</code> from server code.

Step 7: Use Prisma in an API route (App Router)

Create an API route like <code>app/api/users/route.ts</code>. Example CRUD: list users.

ts
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";

export async function GET() {
  const users = await prisma.user.findMany({
    orderBy: { createdAt: "desc" },
    select: {
      id: true,
      email: true,
      name: true,
      createdAt: true,
    },
  });

  return NextResponse.json({ users });
}

Because it runs in <code>app/api/.../route.ts</code>, it is server code.

Step 8: Use Prisma in server components

Server Components can directly call Prisma (as long as you do not import Prisma Client into Client Components). Example <code>app/users/page.tsx</code> (server component):

tsx
import { prisma } from "@/lib/prisma";

export default async function UsersPage() {
  const users = await prisma.user.findMany({
    orderBy: { createdAt: "desc" },
    take: 20,
  });

  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map((u) => (
          <li key={u.id}>
            {u.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

If you need client-side interactivity, keep Prisma calls in server code and pass data down as props.

Step 9: Environment variables and build behavior

Your Prisma schema uses <code>DATABASE_URL</code>. Common pitfalls:

  • Missing <code>DATABASE_URL</code> in production deploy environment
  • Different database credentials between local and production
  • Running migrations at the wrong time

Step 10: Production tips for Prisma with Next.js

1. Avoid creating Prisma Client per request

Use the <code>lib/prisma.ts</code> helper pattern shown above.

2. Understand database connection pooling

Prisma opens connections based on your database driver. In serverless setups, repeated cold starts can create extra connections. Mitigations:

  • Use database providers with good pooling
  • Use a connection pooler (for example PgBouncer for PostgreSQL)

3. Run migrations in CI or during deployment

A common workflow:

  • Build app
  • Apply migrations before or during deploy
  • Start the server

4. Generate Prisma Client after schema changes

Locally you run <code>prisma migrate dev</code>. In production builds, ensure Prisma Client generation happens (it usually does via <code>@prisma/client</code>, but confirm your deployment pipeline).

5. Select only what you need

Use <code>select</code> to reduce payload sizes and improve performance. Example:

ts
await prisma.user.findMany({
  select: { id: true, email: true },
});

Example: End-to-end create user API route

Create <code>app/api/users/route.ts</code> with both GET and POST.

ts
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";

export async function GET() {
  const users = await prisma.user.findMany({
    orderBy: { createdAt: "desc" },
  });

  return NextResponse.json({ users });
}

export async function POST(request: Request) {
  const body = await request.json();
  const { email, name } = body as { email: string; name?: string };

  if (!email) {
    return NextResponse.json(
      { error: "email is required" },
      { status: 400 },
    );
  }

  const user = await prisma.user.create({
    data: { email, name },
  });

  return NextResponse.json({ user }, { status: 201 });
}

Common mistakes

  • Importing Prisma into Client Components
  • Forgetting to add <code>DATABASE_URL</code>
  • Not running migrations after changing <code>schema.prisma</code>
  • Using Prisma Client from shared files that might be imported by the browser

Next steps

  • Add validation (for example zod) before writing to the database
  • Add pagination for list endpoints
  • Add error handling for unique constraint violations
  • Add a simple seed script for local development