Next.js paired with Supabase and Prisma gives you a powerful, type-safe full‑stack setup. Supabase provides a hosted PostgreSQL database with authentication and real‑time features, while Prisma acts as a modern ORM for type‑safe database access. In this guide, you’ll connect them seamlessly.
Prerequisites
- Node.js >= 18
- A Next.js project (TypeScript recommended)
- A Supabase account and project
1. Create and Configure Your Supabase Project
- Log in to app.supabase.com and create a new project.
- Note your Project URL and anon/public and service_role keys from Settings > API.
2. Install Dependencies
In your Next.js root folder, run:
npm install @supabase/supabase-js prisma @prisma/client
Initialize Prisma:
npx prisma init
This creates a prisma/schema.prisma
file and a .env
file.
3. Configure Environment Variables
In .env
(next to schema.prisma
), add your Supabase credentials:
DATABASE_URL="postgresql://postgres:<YOUR_DB_PASSWORD>@db.<PROJECT_REF>.supabase.co:5432/postgres"
SUPABASE_URL="https://<PROJECT_REF>.supabase.co"
SUPABASE_ANON_KEY="<YOUR_ANON_KEY>"
SUPABASE_SERVICE_ROLE_KEY="<YOUR_SERVICE_ROLE_KEY>"
In your Next.js .env.local
:
NEXT_PUBLIC_SUPABASE_URL=$SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=$SUPABASE_ANON_KEY
4. Define Your Prisma Schema
Edit prisma/schema.prisma
:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
createdAt DateTime @default(now())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
authorId String
author User @relation(fields: [authorId], references: [id])
published Boolean @default(false)
createdAt DateTime @default(now())
}
Run migrations:
npx prisma migrate dev --name init
5. Initialize Supabase and Prisma Clients
Create lib/db.ts
:
// lib/db.ts
import { createClient } from "@supabase/supabase-js";
import { PrismaClient } from "@prisma/client";
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
declare global {
// Prevent multiple instances in development
var prisma: PrismaClient | undefined;
}
export const prisma =
global.prisma ||
new PrismaClient({
log: ["query"],
});
if (process.env.NODE_ENV !== "production") global.prisma = prisma;
6. Fetch Data in Next.js API Route
Create pages/api/posts.ts
:
import type { NextApiRequest, NextApiResponse } from "next";
import { supabase, prisma } from "../../lib/db";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "GET") {
// Fetch posts from Supabase
const { data: sbPosts, error: sbError } = await supabase
.from("Post")
.select("*");
if (sbError) return res.status(500).json({ error: sbError.message });
// Alternatively, fetch via Prisma
const prismaPosts = await prisma.post.findMany({
include: { author: true },
});
return res.status(200).json({ supabase: sbPosts, prisma: prismaPosts });
}
res.setHeader("Allow", ["GET"]);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
7. Display in a Next.js Page
In pages/index.tsx
:
import { GetServerSideProps } from "next";
type Post = { id: number; title: string; content: string | null };
export const getServerSideProps: GetServerSideProps = async () => {
const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/posts`);
const { prisma } = await res.json();
return { props: { posts: prisma as Post[] } };
};
export default function Home({ posts }: { posts: Post[] }) {
return (
<main>
<h1>Posts</h1>
<ul>
{posts.map((p) => (
<li key={p.id}>
<h2>{p.title}</h2>
<p>{p.content}</p>
</li>
))}
</ul>
</main>
);
}
Conclusion
You’ve now set up a Next.js app that uses Supabase for hosting and authentication and Prisma for type-safe database access. From here, explore real‑time subscriptions, row‑level security in Supabase, and more advanced Prisma queries!
Combine Supabase and Prisma to deliver a robust, scalable full‑stack experience with minimal boilerplate!