Nextjs prisma postgres demo (ok)
https://github.com/prisma/nextjs-prisma-postgres-demo
Last updated
Was this helpful?
https://github.com/prisma/nextjs-prisma-postgres-demo
Last updated
Was this helpful?
auth.ts
import NextAuth from "next-auth"
import github from "next-auth/providers/github"
import { PrismaAdapter } from "@auth/prisma-adapter"
import prisma from "@/lib/prisma"
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [github],
})
.env
DATABASE_URL="postgresql://postgres:123456@localhost:5432/postgres"
AUTH_SECRET="P3QNKlJz6Gob2ZxKkPyJWPfmeLbV0OkRP/38HVWxgak="
AUTH_GITHUB_ID=Ov23lir84nNhYsRt3vvZ
AUTH_GITHUB_SECRET=41355bd062ff5433da463defcf069ece92567995
prisma\schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
name String?
email String @unique
createdAt DateTime @default(now())
emailVerified DateTime?
image String?
updatedAt DateTime @default(now()) @updatedAt
id Int @id @default(autoincrement())
accounts Account[]
posts Post[]
sessions Session[]
}
model Account {
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([provider, providerAccountId])
}
model Session {
sessionToken String @unique
expires DateTime
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
author User @relation(fields: [authorId], references: [id])
}
prisma\seed.ts
import { PrismaClient, Prisma } from '@prisma/client'
const prisma = new PrismaClient()
const userData: Prisma.UserCreateInput[] = [
{
name: 'Alice1',
email: 'alice1@prisma.io',
posts: {
create: [
{
title: 'Join the Prisma Discord',
content: 'https://pris.ly/discord',
published: true,
},
{
title: 'Prisma on YouTube',
content: 'https://pris.ly/youtube',
},
],
},
},
{
name: 'Bob1',
email: 'bob1@prisma.io',
posts: {
create: [
{
title: 'Follow Prisma on Twitter',
content: 'https://www.twitter.com/prisma',
published: true,
},
],
},
},
{
name: 'Lio1',
email: 'lio1@prisma.io',
posts: {
create: [
{
title: 'Follow Prisma on Twitter',
content: 'https://www.twitter.com/prisma',
published: true,
},
],
},
}
]
export async function main() {
for (const u of userData) {
await prisma.user.create({ data: u })
}
}
main()
lib\utils.ts
import { RegExpMatcher, englishDataset, englishRecommendedTransformers, } from "obscenity";
import type { User } from "@prisma/client";
export function formatName(fullName: User["name"] | undefined): string {
if (!fullName) return "Anonymous User";
const parts = fullName.trim().split(/\s+/);
if (parts.length === 1) return parts[0];
return `${parts[0]} ${parts[parts.length - 1].charAt(0)}.`;
}
const matcher = new RegExpMatcher({
...englishDataset.build(),
...englishRecommendedTransformers,
});
export function containsProfanity(text: string): boolean {
return matcher.hasMatch(text);
}
lib\prisma.ts
import { PrismaClient } from "@prisma/client";
const prismaClientSingleton = () => {
return new PrismaClient();
};
declare const globalThis: {
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();
export default prisma;
if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma;
lib\actions.ts
"use server";
import { auth } from "@/auth";
import prisma from "@/lib/prisma";
import { containsProfanity } from "@/lib/utils";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export async function publishPost(formData: FormData) {
const session = await auth();
if (!session?.user) {
redirect("/");
}
const title = formData.get("title") as string;
const content = formData.get("content") as string;
const postId = formData.get("postId") as string;
if (!title?.trim()) {
throw new Error("Title is required");
}
if (containsProfanity(content)) {
throw new Error("Content contains profanity");
}
const post = await prisma.post.upsert({
where: {
id: parseInt(postId ?? "-1"),
author: {
email: session.user.email!,
},
},
update: {
title: title.trim(),
content: content?.trim(),
published: true,
},
create: {
title: title.trim(),
content: content?.trim(),
published: true,
author: {
connect: {
email: session.user.email!,
},
},
},
});
revalidatePath(`/posts/${post.id}`);
revalidatePath("/posts");
redirect(`/posts/${post.id}`);
}
export async function saveDraft(formData: FormData) {
const session = await auth();
if (!session?.user) {
redirect("/");
}
const title = formData.get("title") as string;
const content = formData.get("content") as string;
const postId = formData.get("postId") as string;
if (!title?.trim()) {
throw new Error("Title is required");
}
if (containsProfanity(content)) {
throw new Error("Content contains profanity");
}
const post = await prisma.post.upsert({
where: {
id: parseInt(postId ?? "-1"),
author: {
email: session.user.email!,
},
},
update: {
title: title.trim(),
content: content?.trim(),
published: false,
},
create: {
title: title.trim(),
content: content?.trim(),
published: false,
author: {
connect: {
email: session.user.email!,
},
},
},
});
revalidatePath(`/posts/${post.id}`);
revalidatePath("/posts");
redirect(`/posts/${post.id}`);
}
components\auth.tsx
import { signIn, signOut } from "@/auth";
export async function SignInButton() {
return (
<form
action={async () => {
"use server";
await signIn("github");
}}
>
<button className="inline-flex items-center gap-2 bg-[#24292F] hover:bg-[#24292F]/90 text-white px-6 py-2.5 rounded-lg font-medium transition-colors">
<svg
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clipRule="evenodd"
/>
</svg>
Sign in with GitHub
</button>
</form>
);
}
export async function SignOutButton() {
return (
<form
action={async () => {
"use server";
await signOut();
}}
>
<button className="block w-full text-left px-4 py-2 text-sm text-gray-600 hover:text-gray-800 font-medium hover:bg-gray-50 transition-colors">
Sign out
</button>
</form>
);
}
components\post.tsx
"use client";
import Form from "next/form";
import Link from "next/link";
import { useFormStatus } from "react-dom";
import { publishPost, saveDraft } from "@/lib/actions";
import { Post } from "@prisma/client";
function SubmitButton({ isPublished }: { isPublished?: boolean }) {
const { pending } = useFormStatus();
return (
<div className="flex gap-4">
<button
type="submit"
disabled={pending}
className="bg-blue-500 hover:bg-blue-600 disabled:bg-blue-300 text-white px-6 py-2 rounded-lg font-medium transition-colors"
>
{isPublished ? "Update Post" : "Publish Post"}
</button>
{!isPublished && (
<button
formAction={saveDraft}
disabled={pending}
className="bg-gray-100 hover:bg-gray-200 disabled:bg-gray-50 text-gray-700 px-6 py-2 rounded-lg font-medium transition-colors"
>
Save as Draft
</button>
)}
</div>
);
}
interface PostFormProps {
post?: Post;
}
export function PostForm({ post }: PostFormProps) {
return (
<>
<div className="flex items-center justify-between mb-8">
<h1 className="text-3xl font-bold text-gray-900">
{!post ? "Create New Post" : "Edit Post"}
</h1>
{!!post && (
<Link
href={`/posts/${post?.id}`}
className="text-gray-600 hover:text-gray-800 font-medium transition-colors"
>
Cancel
</Link>
)}
</div>
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<Form action={publishPost} className="space-y-6">
{post && <input type="hidden" name="postId" value={post.id} />}
<div>
<label
htmlFor="title"
className="block text-sm font-medium text-gray-700 mb-2"
>
Title
</label>
<input
type="text"
id="title"
name="title"
required
defaultValue={post?.title}
placeholder="Enter your post title"
className="w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-shadow"
/>
</div>
<div>
<label
htmlFor="content"
className="block text-sm font-medium text-gray-700 mb-2"
>
Content
</label>
<textarea
id="content"
name="content"
defaultValue={post?.content || ""}
placeholder="Write your post content here..."
rows={8}
className="w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-shadow"
/>
</div>
<div className="flex justify-end pt-4">
<SubmitButton isPublished={post && post.published} />
</div>
</Form>
</div>
</>
);
}
app\page.tsx
import { unstable_noStore as noStore } from "next/cache";
import prisma from "@/lib/prisma";
import Link from "next/link";
import type { Session } from "next-auth";
import Image from "next/image";
import { formatName } from "@/lib/utils"
import { auth } from "@/auth";
import type { User } from "@prisma/client";
import { SignInButton, SignOutButton } from "@/components/auth";
function UserMenu({ user }: { user: NonNullable<Session["user"]> }) {
return (
<div className="relative group">
<button className="flex items-center gap-2 p-2 rounded-lg hover:bg-gray-50 transition-colors">
{user.image ? (
<Image
src={user.image}
alt={formatName(user.name)}
width={32}
height={32}
className="rounded-full"
/>
) : (
<div className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center">
<span className="text-gray-500 text-sm font-medium">
{(user.name || "U").charAt(0)}
</span>
</div>
)}
<span className="text-sm font-medium text-gray-700">
{formatName(user.name)}
</span>
<svg
className="w-4 h-4 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
<div className="absolute right-0 mt-1 w-36 py-1 bg-white rounded-lg shadow-lg border border-gray-100 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all">
<Link
href={`/users/${user.id}`}
className="block w-full text-left px-4 py-2 text-sm text-gray-600 hover:text-gray-800 font-medium hover:bg-gray-50 transition-colors"
>
View profile
</Link>
<SignOutButton />
</div>
</div>
);
}
function UserCard({ user }: { user: User }) {
return (
<Link
href={`/users/${user.id}`}
className="block transition-transform hover:scale-[1.02]"
>
<div className="flex items-center gap-3 p-4 bg-white rounded-lg shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
{user.image ? (
<Image
src={user.image}
alt={formatName(user.name)}
width={40}
height={40}
className="rounded-full"
/>
) : (
<div className="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center">
<span className="text-gray-500 text-sm font-medium">
{(user.name || "User").charAt(0)}
</span>
</div>
)}
<div>
<div className="font-medium text-gray-900">
{formatName(user.name)}
</div>
</div>
</div>
</Link>
);
}
export default async function Home() {
noStore();
const session = await auth();
const users = await prisma.user.findMany({
take: 100
});
return (
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white">
<header className="fixed top-0 left-0 right-0 bg-white/80 backdrop-blur-sm border-b border-gray-100 z-10">
<div className="max-w-7xl mx-auto px-4 h-16 flex items-center justify-between">
<Link href="/" className="text-xl font-bold text-gray-900">
Superblog
</Link>
<div>
{session?.user ? (
<UserMenu user={session.user} />
) : (
<SignInButton />
)}
</div>
</div>
</header>
<main className="max-w-4xl mx-auto px-4 pt-24 pb-16">
<div className="text-center mb-16">
<h1 className="text-5xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-gray-900 via-gray-800 to-gray-900 font-[family-name:var(--font-geist-sans)]">
Superblog
</h1>
<p className="text-gray-600 text-lg max-w-2xl mx-auto">
A demo application showcasing the power of Prisma Postgres and
Next.js
</p>
</div>
<div>
<h2 className="text-2xl font-bold mb-6 text-gray-900">
Community Members
</h2>
<div className="grid gap-4 sm:grid-cols-2">
{users.map((user) => (
<UserCard key={user.id} user={user} />
))}
</div>
</div>
</main>
</div>
);
}
app\users\[id]\page.tsx
import { notFound } from "next/navigation";
import prisma from "@/lib/prisma";
import { auth } from "@/auth";
import Image from "next/image";
import Link from "next/link";
import { formatName } from "@/lib/utils";
export default async function UserProfile({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const session = await auth();
const user = await prisma.user.findUnique({
where: { id: parseInt(id) },
include: {
posts: {
orderBy: { id: "desc" },
},
},
});
if (!user) {
notFound();
}
const isOwnProfile = session?.user?.email === user.email;
const posts = isOwnProfile ? user.posts : user.posts.filter((post) => post.published);
return (
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white">
<div className="max-w-4xl mx-auto px-4 py-16">
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-8 mb-8">
<div className="flex items-center gap-6">
{user.image ? (
<Image
src={user.image}
alt={formatName(user.name)}
width={80}
height={80}
className="rounded-full"
/>
) : (
<div className="w-20 h-20 bg-gray-100 rounded-full flex items-center justify-center">
<span className="text-gray-500 text-2xl font-medium">
{(user.name || "User").charAt(0)}
</span>
</div>
)}
<div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">
{formatName(user.name)}
</h1>
<Link
href="/"
className="text-gray-500 hover:text-gray-700 transition-colors"
>
← Back to all users
</Link>
</div>
</div>
</div>
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">
Published Posts
</h2>
{isOwnProfile && (
<Link
href="/posts/new"
className="inline-flex items-center gap-2 bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg font-medium transition-colors"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 4v16m8-8H4"
/>
</svg>
New Post
</Link>
)}
</div>
{posts.length === 0 ? (
<div className="bg-white rounded-lg border border-gray-100 p-8 text-center">
<p className="text-gray-500 mb-4">
{isOwnProfile
? "You haven't published any posts yet."
: "No published posts yet."}
</p>
{isOwnProfile && (
<Link
href="/posts/new"
className="inline-flex items-center gap-2 text-blue-500 hover:text-blue-600 font-medium transition-colors"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 4v16m8-8H4"
/>
</svg>
Write your first post
</Link>
)}
</div>
) : (
<div className="grid gap-4">
{posts.map((post) => (
<Link
key={post.id}
href={`/posts/${post.id}`}
className="block transition-transform hover:scale-[1.01]"
>
<div className="bg-white rounded-lg shadow-sm border border-gray-100 p-6 hover:shadow-md transition-shadow">
<div className="flex items-center justify-between mb-2">
<h3 className="text-xl font-semibold text-gray-900">
{post.title}
</h3>
{!post.published && (
<span className="px-2 py-1 text-xs font-medium bg-gray-100 text-gray-600 rounded">
Draft
</span>
)}
</div>
{post.content && (
<p className="text-gray-600 line-clamp-2">
{post.content}
</p>
)}
</div>
</Link>
))}
</div>
)}
</div>
</div>
</div>
);
}
app\posts\page.tsx
import prisma from "@/lib/prisma";
import Link from "next/link";
import { formatName } from "@/lib/utils";
export default async function Posts() {
const posts = await prisma.post.findMany({
include: {
author: true,
},
where: {
published: true,
},
orderBy: {
createdAt: "desc",
}
});
return (
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white">
<div className="max-w-4xl mx-auto px-4 py-16">
<div className="flex justify-between items-center mb-8">
<h1 className="text-4xl font-bold text-gray-900">Posts</h1>
<Link
href="/posts/new"
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors"
>
New Post
</Link>
</div>
<div className="space-y-4">
{posts.map((post) => (
<Link
key={post.id}
href={`/posts/${post.id}`}
className="block transition-transform hover:scale-[1.01]"
>
<article className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
<h2 className="text-xl font-semibold text-gray-900 mb-2">
{post.title}
</h2>
<div className="text-sm text-gray-500">
by {formatName(post.author.name)}
</div>
</article>
</Link>
))}
</div>
</div>
</div>
);
}
app\posts\new\page.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";
import { PostForm } from "@/components/post";
export default async function NewPostPage() {
const session = await auth();
if (!session?.user) {
redirect("/");
}
return (
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white pt-24">
<div className="max-w-2xl mx-auto px-4">
<PostForm />
</div>
</div>
);
}
app\posts\[id]\page.tsx
import { notFound } from "next/navigation";
import prisma from "@/lib/prisma";
import Image from "next/image";
import Link from "next/link";
import { formatName } from "@/lib/utils";
import { auth } from "@/auth";
export default async function Post({
params,
}: {
params: Promise<{ id: string }>;
}) {
const session = await auth();
const { id } = await params;
const post = await prisma.post.findUnique({
where: { id: parseInt(id) },
include: {
author: true,
},
});
if (!post) {
notFound();
}
const isAuthor = session?.user?.email === post.author.email;
return (
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white">
<div className="max-w-4xl mx-auto px-4 py-16">
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-8">
<article>
<header className="mb-8">
<div className="flex items-center justify-between mb-6">
<h1 className="text-4xl font-bold text-gray-900">
{post.title}
</h1>
{isAuthor && (
<Link
href={`/posts/${post.id}/edit`}
className="bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-lg font-medium transition-colors"
>
Edit Post
</Link>
)}
</div>
{!post.published && (
<div className="mb-6 bg-yellow-50 text-yellow-800 px-4 py-2 rounded-md text-sm">
This post is currently a draft
</div>
)}
<div className="flex items-center gap-3">
{post.author.image ? (
<Image
src={post.author.image}
alt={formatName(post.author.name)}
width={40}
height={40}
className="rounded-full"
/>
) : (
<div className="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center">
<span className="text-gray-500 text-sm font-medium">
{(post.author.name || "User").charAt(0)}
</span>
</div>
)}
<Link
href={`/users/${post.authorId}`}
className="text-gray-600 hover:text-gray-900 transition-colors"
>
By {formatName(post.author.name)}
</Link>
</div>
</header>
{post.content && (
<>
<div className="h-px bg-gray-100 mb-8" />
<div className="prose prose-gray max-w-none">
{post.content}
</div>
</>
)}
</article>
<div className="border-t border-gray-100 mt-12 pt-6">
<Link
href={`/users/${post.authorId}`}
className="text-gray-500 hover:text-gray-700 transition-colors"
>
← Back to author's profile
</Link>
</div>
</div>
</div>
</div>
);
}
app\posts[id]\edit\page.tsx
import { auth } from "@/auth";
import { PostForm } from "@/components/post";
import prisma from "@/lib/prisma";
import { notFound, redirect } from "next/navigation";
export default async function EditPost({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const session = await auth();
if (!session?.user) {
redirect("/");
}
const post = await prisma.post.findUnique({
where: { id: parseInt(id) },
include: { author: true },
});
if (!post) {
notFound();
}
// Verify the user is the author
if (post.author.email !== session.user.email) {
notFound();
}
return (
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white pt-24">
<div className="max-w-2xl mx-auto px-4">
<PostForm post={post} />
</div>
</div>
);
}
app\api\auth\[...nextAuth]\route.ts
import { handlers } from "@/auth"
export const { GET, POST } = handlers
Source:
This example demonstrates how to build a full-stack web application using Next.js, Prisma Client, and Prisma Postgres.
TL;DR: Prisma Postgres is a new kind of Postgres database that's optimized for developer productivity. It offers instant provisioning, built-in caching and real-time events, and seamless integration with Prisma ORM.
If you just want to run the app locally, rename .env.example
to .env
and fill in the values.
1.1 Create a Prisma Postgres instance
Go to the Console and create a new Prisma Postgres instance. Use the DATABASE_URL
and PULSE_API_KEY
values from the new instance to fill out the .env
file.
1.2 Create a GitHub OAuth app
Go to the GitHub Developer Settings and create a new OAuth app.
For the required fields:
Application name and homepage URL can be whatever you want.
Authorization callback URL should be http://localhost:3000/api/auth/callback/github
After creating the app, you'll be redirected to the app's page. Copy the Client ID
and Client Secret
values and use them to fill out AUTH_GITHUB_ID
and AUTH_GITHUB_SECRET
in the .env
file.
1.3 Fill out Auth.js secrets
Run npx auth secret --copy
to generate a new AUTH_SECRET
value. Fill out the .env
file with the new value.
Install npm dependencies:
npm install
Run the following command to create your database. This also creates the needed tables that are defined in prisma/schema.prisma
:
npx prisma migrate dev --name init
When npx prisma migrate dev
is executed against a newly created database, seeding is also triggered. The seed file in prisma/seed.ts
will be executed and your database will be populated with the sample data.
If you switched to Prisma Postgres in the previous step, you need to trigger seeding manually (because Prisma Postgres already created an empty database instance for you, so seeding isn't triggered):
npx prisma db seed
npm run dev
The server is now running on http://localhost:3000
.
Here are some ways to learn more and expand upon this example:
🚀 Deploy your app to Vercel in just a few clicks
📚 Learn more about Prisma ORM and database workflows.
🔍 Explore the Prisma Client API to add more database features.
⭐ Check out more Prisma examples for inspiration.