DEV Community

Cover image for Setting up Prisma with Supabase in Next.js 15: A Complete Developer's Guide
Laxman Rathod
Laxman Rathod

Posted on

Setting up Prisma with Supabase in Next.js 15: A Complete Developer's Guide

Building modern full-stack applications just got easier with this powerful combination of tools

Hey developers! 👋 Ready to supercharge your Next.js 15 application with a robust database setup? Today we're diving deep into integrating Prisma ORM with Supabase's PostgreSQL database, using PNPM as our package manager. By the end of this guide, you'll have a rock-solid foundation for your next project.

Why This Stack Rocks

Before we jump into the code, let's talk about why this combination is absolutely fantastic:

  • Next.js 15: The latest and greatest React framework with improved performance and developer experience
  • Prisma: Type-safe database access with incredible developer tooling
  • Supabase: Open-source Firebase alternative with a powerful PostgreSQL database
  • PNPM: Lightning-fast, disk space efficient package manager that's becoming the new standard

This stack gives you type safety from database to frontend, excellent performance, and a delightful developer experience. Let's get started!

Prerequisites

Before we begin, make sure you have:

  • Node.js 18+ installed
  • PNPM installed globally (npm install -g pnpm)
  • A Supabase account (free tier works perfectly)
  • Your favorite code editor ready to go

Step 1: Setting Up Your Next.js 15 Project

Let's start by creating a fresh Next.js 15 project using PNPM:

pnpm create next-app@latest my-prisma-supabase-app
cd my-prisma-supabase-app
Enter fullscreen mode Exit fullscreen mode

When prompted, choose your preferred options. For this guide, I recommend:

  • TypeScript: Yes
  • ESLint: Yes
  • Tailwind CSS: Yes (optional, but great for styling)
  • App Router: Yes
  • Import alias: Yes

Step 2: Installing Prisma and Dependencies

Now let's install all the packages we need:

# Install Prisma CLI and client
pnpm add prisma @prisma/client

# Optional install additional dependencies for our setup
pnpm add @supabase/supabase-js dotenv
Enter fullscreen mode Exit fullscreen mode

Pro tip: PNPM's speed really shines here compared to npm or yarn!

Step 3: Setting Up Supabase

Head over to supabase.com and create a new project:

  1. Click "New Project"
  2. Choose your organization
  3. Enter a project name
  4. Set a database password (keep it safe!)
  5. Choose your region
  6. Click "Create new project"

Wait for Supabase to set up your database (usually takes 1-2 minutes). Once ready, grab your connection details:

  1. Go to Settings → Database
  2. Copy the connection string under "Connection string"
  3. Also note down your project URL and anon key from Settings → API

Step 4: Environment Configuration

Create a .env.local file in your project root:

# Database connection string from Supabase
DATABASE_URL="postgresql://postgres:YOUR_PASSWORD@YOUR_PROJECT_REF.supabase.co:5432/postgres?pgbouncer=true&connection_limit=1"

# For direct connection (migrations)
DIRECT_URL="postgresql://postgres:YOUR_PASSWORD@YOUR_PROJECT_REF.supabase.co:5432/postgres"

# Supabase public configuration
NEXT_PUBLIC_SUPABASE_URL="https://YOUR_PROJECT_REF.supabase.co"
NEXT_PUBLIC_SUPABASE_ANON_KEY="your_anon_key_here"
Enter fullscreen mode Exit fullscreen mode

Important notes:

  • Replace YOUR_PASSWORD, YOUR_PROJECT_REF, and your actual keys
  • The DATABASE_URL uses pgbouncer for connection pooling
  • The DIRECT_URL is needed for migrations
  • Never commit your .env.local file to version control!

Step 5: Initializing Prisma

Initialize Prisma in your project:

pnpm prisma init
Enter fullscreen mode Exit fullscreen mode

This creates:

  • A prisma folder with schema.prisma
  • Updates your .env file (you can replace it with your .env.local content)

Step 6: Configuring Prisma Schema

Open prisma/schema.prisma and update it:

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

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

datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("DIRECT_URL")
}

// Example models - customize based on your needs
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  posts     Post[]

  @@map("users")
}

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

  @@map("posts")
}
Enter fullscreen mode Exit fullscreen mode

Step 7: Creating and Running Your First Migration

Now for the exciting part - let's create our database schema:

# Create and apply your first migration
pnpm prisma migrate dev --name init
Enter fullscreen mode Exit fullscreen mode

This command:

  1. Creates a new migration file in prisma/migrations
  2. Applies the migration to your Supabase database
  3. Generates the Prisma client

You should see output confirming the migration was successful!

Step 8: Setting Up Prisma Client

Create a lib/prisma.ts file to set up your Prisma client:

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

declare const globalThis: {
  prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;

const prismaClientSingleton = () => {
  return new PrismaClient();
};

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

if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma;
Enter fullscreen mode Exit fullscreen mode

This setup prevents multiple Prisma client instances during development hot reloads.

Step 9: Creating Your First API Route

Let's create an API route to test everything works. Create app/api/users/route.ts:

import { prisma } from '@/lib/prisma'
import { NextResponse } from 'next/server'

export async function GET() {
  try {
    const users = await prisma.user.findMany({
      include: {
        posts: true,
      },
    })
    return NextResponse.json(users)
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to fetch users' },
      { status: 500 }
    )
  }
}

export async function POST(request: Request) {
  try {
    const { name, email } = await request.json()

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

    return NextResponse.json(user, { status: 201 })
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to create user' },
      { status: 500 }
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 10: Testing Your Setup

Let's create a simple page to test our setup. Update app/page.tsx:

'use client'

import { useState, useEffect } from 'react'

interface User {
  id: string
  name: string | null
  email: string
  posts: any[]
}

export default function Home() {
  const [users, setUsers] = useState<User[]>([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => {
        setUsers(data)
        setLoading(false)
      })
  }, [])

  const createTestUser = async () => {
    const response = await fetch('/api/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        name: 'John Doe',
        email: `johndoe@example.com`,
      }),
    })

    if (response.ok) {
      const newUser = await response.json()
      setUsers([...users, newUser])
    }
  }

  if (loading) return <div className="p-8">Loading...</div>

  return (
    <div className="p-8">
      <h1 className="text-2xl font-bold mb-4">Prisma + Supabase + Next.js 15</h1>

      <button 
        onClick={createTestUser}
        className="bg-blue-500 text-white px-4 py-2 rounded mb-4 hover:bg-blue-600"
      >
        Create User
      </button>

      <div>
        <h2 className="text-xl font-semibold mb-2">Users ({users.length})</h2>
        {users.map(user => (
          <div key={user.id} className="border p-3 mb-2 rounded">
            <p><strong>Name:</strong> {user.name || 'No name'}</p>
            <p><strong>Email:</strong> {user.email}</p>
            <p><strong>Posts:</strong> {user.posts.length}</p>
          </div>
        ))}
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Start your development server:

pnpm dev
Enter fullscreen mode Exit fullscreen mode

Visit http://localhost:3000 and test creating a user!

Working with Prisma Migrations

Here are the essential migration commands you'll use regularly:

Creating New Migrations

When you modify your schema:

# Create a new migration
pnpm prisma migrate dev --name add_user_profile

# Reset database (careful in production!)
pnpm prisma migrate reset
Enter fullscreen mode Exit fullscreen mode

Viewing Migration Status

# See migration status
pnpm prisma migrate status

# View your data
pnpm prisma studio
Enter fullscreen mode Exit fullscreen mode

Production Deployments

For production:

# Deploy pending migrations
pnpm prisma migrate deploy

# Generate Prisma client
pnpm prisma generate
Enter fullscreen mode Exit fullscreen mode

Best Practices and Pro Tips

1. Schema Design

  • Use meaningful model and field names
  • Add database constraints where appropriate
  • Consider indexing for frequently queried fields
  • Use enums for predefined values

2. Migration Management

  • Always review migration files before applying
  • Use descriptive migration names
  • Never edit migration files manually
  • Keep migrations small and focused

3. Performance Optimization

  • Use connection pooling (already configured with Supabase)
  • Implement proper indexing
  • Use select to limit returned fields
  • Consider using findFirst instead of findMany when appropriate

4. Error Handling

try {
  const user = await prisma.user.create({ data: userData })
  return user
} catch (error) {
  if (error instanceof Prisma.PrismaClientKnownRequestError) {
    // Handle specific Prisma errors
    if (error.code === 'P2002') {
      throw new Error('Email already exists')
    }
  }
  throw error
}
Enter fullscreen mode Exit fullscreen mode

5. Type Safety

import type { User, Post } from '@prisma/client'

type UserWithPosts = User & {
  posts: Post[]
}
Enter fullscreen mode Exit fullscreen mode

Package.json Scripts

Add these helpful scripts to your package.json:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "db:generate": "prisma generate",
    "db:push": "prisma db push",
    "db:migrate": "prisma migrate dev",
    "db:studio": "prisma studio",
    "db:seed": "tsx prisma/seed.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

Troubleshooting Common Issues

Connection Issues

  • Double-check your DATABASE_URL and DIRECT_URL
  • Ensure your Supabase project is running
  • Verify your IP is allowed (Supabase allows all by default)

Migration Errors

  • Check if your schema changes are valid
  • Ensure you're not removing required fields without proper migration steps
  • Review the generated migration SQL

Type Errors

  • Run pnpm prisma generate after schema changes
  • Restart your TypeScript server in your editor
  • Check for naming conflicts

Next Steps

Congratulations! 🎉 You now have a solid foundation with Prisma, Supabase, and Next.js 15. Here's what you might want to explore next:

  • Authentication: Add Supabase Auth for user management
  • Real-time Features: Implement Supabase real-time subscriptions
  • File Storage: Use Supabase Storage for file uploads
  • Advanced Queries: Explore Prisma's powerful querying capabilities
  • Testing: Set up unit tests for your database operations
  • Deployment: Deploy to Vercel with proper environment variables

Wrapping Up

This powerful combination of technologies gives you everything needed to build modern, scalable applications. The type safety from Prisma, the reliability of PostgreSQL through Supabase, and the performance of Next.js 15 create an excellent developer experience.

The setup might seem like a lot initially, but once it's running, you'll appreciate the robust foundation it provides.
Happy coding, and feel free to reach out if you have questions!


Found this guide helpful? Share it with your fellow developers and let me know what you build with this stack!

Top comments (0)