Step 1: Connect Databse
.env
Copy DATABASE_URL="postgresql://postgres:123456@localhost:5432/practice"
AUTH_SECRET="P3QNKlJz6Gob2ZxKkPyJWPfmeLbV0OkRP/38HVWxgak="
AUTH_GITHUB_ID=Ov23lir84nNhYsRt3vvZ
AUTH_GITHUB_SECRET=41355bd062ff5433da463defcf069ece92567995
β Your Prisma schema was created at prisma/schema.prisma You can now open it in your favorite editor.
warn Prisma would have added DATABASE_URL but it already exists in .env warn You already have a .gitignore file. Don't forget to add .env
in it to not commit any private information.
Next steps:
Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver, mongodb or cockroachdb.
Run prisma db pull to turn your database schema into a Prisma schema.
Run prisma generate to generate the Prisma Client. You can then start querying your database.
Tip: Explore how you can extend the ORM with scalable connection pooling, global caching, and real-time database events. Read: https://pris.ly/cli/beyond-orm
More information in our documentation: https://pris.ly/d/getting-started
Step 2: Create schema.prisma auto && seed
(Δiα»u ΔΓ³ cΓ³ nghΔ©a bαΊ‘n ΔΓ£ cΓ³ sαΊ΅n database postgres giα» bαΊ‘n muα»n lΓ m database practice cΕ©ng cΓ³ cαΊ₯u trΓΊc giα»ng thαΊΏ hΓ£y dΓΉng lα»nh prisma db pull
Sau ΔΓ³ ta chuyα»n sang database prictice rα»i thα»±c hiα»n lα»nh prisma db push
KαΊΏt quαΊ£ nhΖ° nΓ y
prisma\seed.ts
Copy 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()
ChΓΊ Γ½ ta phαΊ£i chuyα»n prisma\seed.ts
Vì node_modules.prisma\client\index.d.ts nó tự sinh ra
node_modules.prisma\client\index.d.ts
Copy export type UserCreateInput = {
name?: string | null
email: string
createdAt?: Date | string
emailVerified?: Date | string | null
image?: string | null
updatedAt?: Date | string
Account?: AccountCreateNestedManyWithoutUserInput
Post?: PostCreateNestedManyWithoutUserInput
Session?: SessionCreateNestedManyWithoutUserInput
}
do ΔΓ³ trong prisma\seed.ts cΕ©ng phαΊ£i chuyα»n sang Post
Sau ΔΓ³ thα»±c hiα»n lα»nh npx prisma db seed
KαΊΏt quαΊ£:
Step 3: Show data to front-end
app\page.tsx
Copy import Image from "next/image";
import Link from "next/link";
import { unstable_noStore as noStore } from "next/cache";
import db from "@/lib/db";
import type { User } from "@prisma/client";
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={user.name || "image"}
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">
{user.name}
</div>
</div>
</div>
</Link>
);
}
export default async function Home() {
noStore();
const users = await db.user.findMany({
take: 100
});
return (
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white">
<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>
);
}
Step 4: Login && Permission
auth.ts
Copy import NextAuth from "next-auth";
import github from "next-auth/providers/github";
import { PrismaAdapter } from "@auth/prisma-adapter";
import db from "@/lib/db";
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(db),
providers: [github],
})
app\page.tsx
Copy import Image from "next/image";
import Link from "next/link";
import type { Session } from "next-auth";
import { unstable_noStore as noStore } from "next/cache";
import db from "@/lib/db";
import type { User } from "@prisma/client";
import { SignInButton, SignOutButton } from "@/components/auth";
import { auth } from "@/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={user.name || "hello"}
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">
{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={user.name || "image"}
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">
{user.name}
</div>
</div>
</div>
</Link>
);
}
export default async function Home() {
noStore();
const session = await auth();
const users = await db.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>
);
}
components\auth.tsx
Copy 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>
);
}