ProgrUmar Logo
Module 6: Database Integration

Prisma ORM with Next.js

Duration: 28 mins

Prisma in Next.js

Define your schema in schema.prisma, run prisma migrate dev, and import the Prisma Client in your Server Components or Server Actions. Avoid instantiating a new client on every hot reload by using the global singleton pattern.

Prisma ORM with Next.js

Prisma is a modern, type-safe Object-Relational Mapper (ORM) that simplifies database integrations using a declaratively defined database schema. In a Next.js environment, Prisma auto-generates TypeScript types matching your database structures, allowing you to write queries with complete autocomplete support. In this lesson, we will cover how to design schema tables, write migrations, configure the Prisma Client using a global singleton to prevent hot-reload connection leaks, and perform data reads and writes inside Server Components and Actions.


1. Setting Up Prisma inside Next.js

To integrate Prisma into a Next.js 15 project, install the Prisma CLI and the client packages:

npm install @prisma/client
npm install -D prisma

Initialize Prisma config directories:

npx prisma init

This command scaffolds a prisma/ folder at your project root containing a schema.prisma file, and adds a .env file containing your database connection string template.


2. Modeling Tables in schema.prisma

The schema.prisma file serves as the single source of truth for your database layout. You define your database engine source (e.g. PostgreSQL), type generators, and data models:

// prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now())
}

3. Generating Migrations and Prisma Client

Once you define or edit models in schema.prisma, run the Prisma CLI to generate a migration file and apply modifications to your live database schema:

npx prisma migrate dev --name init

This command:

  • Generates a SQL migration file in prisma/migrations/.
  • Executes the SQL queries to update your live database.
  • Generates the local Prisma Client library, updating its TypeScript typings.

4. The Global Client Singleton Pattern

In Next.js development mode, files are hot-reloaded on every edit. If you instantiate the Prisma Client directly in your data helper files, code edits will run constructor triggers repeatedly:

// BAD: DO NOT DO THIS
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient();

This locks up database sockets within minutes. Instead, use a global singleton pattern to preserve a single client instance:

// lib/prisma.ts
import { PrismaClient } from '@prisma/client';

const prismaClientSingleton = () => {
  return new PrismaClient({
    log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
  });
};

declare global {
  var prismaGlobal: undefined | ReturnType<typeof prismaClientSingleton>;
}

export const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();

if (process.env.NODE_ENV !== 'production') {
  globalThis.prismaGlobal = prisma;
}

5. Querying Database in async Server Components

Because Server Components compile server-side, you can import the prisma client and execute queries directly within your components:

// app/blog/page.tsx
import { prisma } from '@/lib/prisma';

export default async function BlogIndexPage() {
  // Query only published posts, fetching the author relation
  const posts = await prisma.post.findMany({
    where: { published: true },
    include: {
      author: {
        select: { name: true, email: true },
      },
    },
    orderBy: { createdAt: 'desc' },
  });

  return (
    <main className="p-8">
      <h1 className="text-3xl font-bold mb-6">Course Articles</h1>
      <div className="space-y-4">
        {posts.map((post) => (
          <article key={post.id} className="border p-4 rounded-xl">
            <h2 className="text-xl font-bold">{post.title}</h2>
            <p className="text-sm text-neutral-500">
              By {post.author.name || 'Anonymous'}
            </p>
          </article>
        ))}
      </div>
    </main>
  );
}

6. Handling Data Mutations with Server Actions

Write Server Actions to handle mutations. Prisma will type-check parameters automatically, ensuring you do not run queries with malformed types:

// app/actions/post-actions.ts
'use server';

import { prisma } from '@/lib/prisma';
import { revalidatePath } from 'next/cache';

export async function createPostAction(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;
  const authorId = 1; // Replace with session authenticated user ID

  if (!title || title.trim() === '') {
    throw new Error('Title is required.');
  }

  await prisma.post.create({
    data: {
      title,
      content,
      published: true,
      authorId,
    },
  });

  // Revalidate cache to display new post instantly on blog path
  revalidatePath('/blog');
}

7. Optimizing Queries to Minimize Payload Weight

Avoid querying entire columns if they are not needed in the layout. Prisma supports select-filtering which limits SQL payloads to the exact fields defined:

// Selecting specific fields
const userList = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    // Avoids loading large metadata fields
  },
});

8. The Prisma Edge Compatibility Dilemma

Standard Prisma Client uses a Rust-based query engine binary that runs on Node.js but crashes in Next.js Edge Middleware or Cloudflare Worker runtimes.

If your application requires database queries within Edge layouts, you must utilize the @prisma/extension-accelerate extension or access your database via HTTP request APIs.


9. Common Gotchas

  • Socket Exhaustion during Live Reloads: Forgetting to implement the global client singleton in Next.js dev mode will exhaust your database pool limit in minutes, blocking your dev server.
  • N+1 Query Problems: Fetching users and then loop-mapping them to query their posts individually results in multiple SQL requests. Always use the include block to join tables in a single transaction.
  • Schema Out-of-Sync Errors: If you run schema changes manually on your database without executing prisma generate, client queries will throw database schema out-of-sync warnings.

Key Takeaways

  • Prisma uses the schema.prisma file as the single definition layout for your entire database schema.
  • Use npx prisma migrate dev to apply database alterations and compile type-safe database clients.
  • Implement the global client singleton pattern to preserve connection pools across development hot reloads.
  • Import the Prisma client and query databases directly inside Server Components without writing REST endpoints.
  • Limit network overhead by choosing exact field listings inside the select parameter.

Prisma simplifies schema management and autocomplete validation. However, its heavy Rust query engine can affect cold start performance in serverless platforms. In the next lesson, we will explore Drizzle ORM — A Lighter Alternative, building edge-compatible schemas using pure TypeScript.

Chat with us