مدونة بأستخدام NextAuth و Prisma (PostgreSQL)
Mohanad Alrwaihy
April 24, 2023
186
1
في هذا المنشور ، سنقوم بإنشاء مشروع Simple Blog Post باستخدام قاعدة بيانات NextAuth و PostgreSQL مع Prisma 💐
11 دقائق للقراءة
ما هو NextAuth؟
NextAuth هي واحدة من أفضل الأدوات للتعامل مع المصادقة عند استخدام Next.js لإنشاء مواقع ويب. لأنه سهل الاستخدام ، يقدم التكيف وأضافة الكثير من خيارات قواعد البيانات لحفظ المستخدمين واستمرار الجلسات.
خيارات المصادقة في NextAuth:
- موفرو OAuth - ( GitHub و Twitter و Google وما إلى ذلك ).
- موفر OAuth مخصص.
- استخدام البريد الإلكتروني - الروابط السحرية.
- استخدام طريقة تسجيل الدخول التقليدية - اسم المستخدم وكلمة المرور أو أي بيانات اعتماد عشوائية.
ميزات NextAuth
- الأمان 🔒 - NextAuth تعزز عدم أستخدام طرق بأستخدام كلمات السر.
- رموز تزوير طلب عبر المواقع ( CSRF ) على مسارات POST ( تسجيل الدخول ، تسجيل الخروج ).
- تهدف سياسات ملفات تعريف الارتباط (Cookie) إلى أكثر السياسات تقييدًا.
- رموز الويب JSON ( JWT ) مشفرة باستخدام A256GCM.
اقرأ المزيد عن NextAuth هنا.
تدعم NextAuth استراتيجيتين لجلسة المستخدم JWTs وجلسة قاعدة البيانات.
JWTs وجلسة قاعدة البيانات
يستخدم NextAuth الJWT بشكل أفتراضي لحفظ جلسة المستخدم. بينما يستخدم Database Session عند استخدام NextAuth مع قاعدة بيانات.
لدي منشور مدونة يتحدث عن JWTs و Database Session إذا كنت تريد معرفة المزيد عنه أنقر هنا.
معلومات عن Prisma
ما هو Prisma؟
بأختصار Prisma يصنف ك (Object Relation Mappin) ORM الذي يجعل العمل مع قواعد البيانات مهمة سهلة بسبب القدرة على إنشاء النماذج بطريقة نظيفة وترحيل النماذج إلى قاعدة البيانات ، ويوفر Type Safety عند التعامل مع TypeScript, والإكمال التلقائي.
مخطط Prisma
'مخطط Prisma بديهي ويتيح لك بناء جداول قاعدة البيانات الخاصة بك بطريقة يمكن قراءتها بواسطة الإنسان — مما يجعل تجربة نمذجة البيانات الخاصة بك ممتعة. يمكنك تحديد نماذجك يدويًا أو أستعمالها من قاعدة بيانات موجودة.'
قواعد بيانات Prisma
هذه هي قاعدة البيانات المدعومة:
- PostgreSQL
- MySQL
- SQLite
- SQL Server
- MongoDB
- CockroachDB
مشروع مدونة
سنقوم بإنشاء تطبيق بسيط Blog مع NextAuth و Prisma باستخدام Google OAuth Provider
هذا هو المظهر النهائي للمشروع 👇
بناء مشروع NextJS
POWERSHELLnpx create create-next-app@latestسأقوم بتسمية التطبيق prisma_blog والتحقق من هذه الخيارات :
- TypeScript ✅
- TailwindCSS ✅
- ESlint ✅
التعديلات الأساسية
بعد اكتمال التثبيت سأقوم بتنظيف ملف index.tsx و globals.css
index.tsxTSX/* index.tsx */
export default function Home() {
return (
<div>
<h1>Hello World</h1>
</div>
)
}globals.cssCSS/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply py-1 px-4 shadow-sm hover:shadow-md transition-all font-semibold rounded-md outline-none border-none ring-offset-2 ring-offset-neutral-950 tracking-wide focus-visible:ring-2 focus:scale-95 disabled:cursor-not-allowed disabled:opacity-75 disabled:shadow-none bg-teal-400 shadow-teal-800 hover:bg-teal-500 hover:shadow-teal-800 text-black ring-teal-400 disabled:hover:bg-teal-400;
}
.btn-red {
@apply bg-red-400 shadow-red-800 hover:bg-red-500 hover:shadow-red-800 text-black ring-red-400 disabled:hover:bg-red-400;
}إضافة عنصر Layout.tsx الذي يعرض Nav.tsx و children في ملف app.tsx_:
Layout.tsxTSX// Layout.tsx
import React from 'react'
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div
className={`flex min-h-screen flex-col items-start p-3 max-w-6xl mx-auto ${inter.className}`}
>
<Nav />
{children}
</div>
)
}_app.tsxTSX// _app.tsx
import Layout from '@/components/Layout'
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return (
<Layout>
<Head>
<title>Prisma Blog</title>
</Head>
<Component {...pageProps} />
</Layout>
)
}هذا هيا الأعدادات الرئيسية لتطبيقنا. سيتم العثور على أي إضافة في Repository.
الصفحات 📃
ستكون هناك 3 صفحات في الموقع:
- الصفحة الرئيسية -
./pages/index.tsx - مسودة -
./pages/draft.tsx - النشر -
./pages/post.tsx
أضف NextAuth
سوف أقوم بأتباع التعليمات في NextAuth
ابدأ بتثبيت next-auth:
POWERSHELLnpm install next-auth
إضافة NextAuth API Route:
pages/api/auth/[...nextauth].tsTSX/* pages/api/auth/[...nextauth].ts */
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
export const authOptions = {
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID || '',
clientSecret: process.env.GOOGLE_SECRET || '',
}),
// ...add more providers here
],
}
export default NextAuth(authOptions)إضافة معرف Google و Google Secret
نحن بحاجة إلى الحصول على GOOGLE_ID و GOOGLE_SECRET. للحصول على هذا ، نحتاج إلى التوجه إلى Google Cloud Console وإنشاء مشروع جديد.
إذا لم يكن لديك حساب Google Cloud ، يمكنك إنشاء حساب مجانًا ، عليك إضافة طريقة دفع لإنشاء الحساب ولكن لن تفرض أي رسوم إذا كنت تعرف ما تفعل 🙂 ( كن حذرًا لعدم استخدام أي خدمات حتى تقرأ رسوم الخدمة ) في حالتنا ، سنضيف OAuth 2.0 المجاني للاستخدام.
نصيحة
إذا لم يكن لديك حساب Google Cloud ، يمكنك إنشاء حساب مجانًا ، عليك إضافة طريقة دفع لإنشاء الحساب ولكن لن تفرض أي رسوم إذا كنت تعرف ما تفعل 🙂 ( كن حذرًا لعدم استخدام أي خدمات حتى تقرأ رسوم الخدمة ) في حالتنا ، سنضيف OAuth 2.0 المجاني للاستخدام.
لإنشاء مشروع جديد ، يمكنك كتابة New Project في شريط البحث العلوي وتحديد الخيار الأول:
اختر أسم للمشروع واضغط على Create:
اكتب OAuth واختر Credentials:
في أعلى انقر Create Credentials ثم OAuth client ID:
يجب عليك تعديل اعدادات Consent Screen أولاً:
- Configure consent screen
- User type - External
- Create
الآن علينا إضافة معلومات التطبيق 👇
- اسم التطبيق - Prisma Blog
- البريد الإلكتروني لدعم المستخدم - أي بريد إلكتروني
- معلومات الاتصال بالمطور - بريدك الإلكتروني أو اي شيء
- حفظ ومتابعة.
بمجرد انتهاء من تعديل Consent screen سوف نعود إلى Credentials ثم OAuth client ID.
هذا مثال على كيفية ملء الحقول المطلوبة 👇
- نوع التطبيق - تطبيق ويب.
- الاسم - يمكنك وضع أي اسم هنا ويمكنك إنشاء بيانات اعتماد OAuth متعددة لبيئات مختلفة.
- أصول JavaScript المعتمدة - عنوان URL الأصلي للتطوير سيكون http://localhost:3000 تأكد من إضافة نطاقك الفعلي في الProduction.
- الURIs المعتمدة لأعادة التوجيه - مع NextAuth سيكون عنوان URL الأساسي متبوعًا بـ
BASE_URL/api/auth/callback/ providerفي هذه الحالة يكون الموفر هو google. - انشاء!
- بمجرد إنشائك سترى معرف العميل و سر العميل الذي نريد استخدامه في تطبيقنا 👍
إضافة Session Provider & useSession
SessionProvider 👇
_app.tsxTSX// _app.tsx
import Layout from '@/components/Layout'
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import { SessionProvider } from 'next-auth/react'
export default function App({ Component, pageProps }: AppProps) {
return (
<SessionProvider session={pageProps.session}>
<Layout>
<Head>
<title>Prisma Blog</title>
</Head>
<Component {...pageProps} />
</Layout>
</SessionProvider>
)
}في شريط Nav ، سنستخدم useSession للقيام بعملية تسجيل الدخول و تسجيل الخروج و عرض معلومات الجلسة 👇
Nav.tsxTSX/* Nav.tsx */
import { signIn, signOut, useSession } from 'next-auth/react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import Button from './ui/Button'
export default function Nav() {
const { data: session } = useSession()
const asPath = useRouter().asPath
function cn(...classes: string[]) {
return classes.filter(Boolean).join(' ')
}
const navigation = [
{ title: 'Home', href: '/' },
{ title: 'Draft', href: '/draft' },
{ title: 'Post', href: '/post' },
]
return (
<nav className='p-5 mb-20 rounded-md shadow-lg bg-neutral-950 text-neutral-200 w-full'>
<ul className='flex items-center gap-5 font-medium'>
{navigation.map(({ title, href }) => (
<li key={title}>
<Link
className={cn(
'py-2 px-4 rounded-lg',
asPath === href
? 'text-teal-400 underline underline-offset-8 cursor-default'
: 'hover:underline underline-offset-4'
)}
href={href}
>
{title}
</Link>
</li>
))}
<li className='ml-auto flex gap-2 text-sm'>
{!session ? (
<button className='btn' onClick={() => signIn('google')}>
Sign in
</button>
) : (
<>
<img
src={session.user?.image || ''}
alt={session.user?.name || 'User Avatar'}
className='w-8 h-8 rounded-md cursor-pointer hover:ring ring-teal-400 mr-2'
/>
<button className='btn' onClick={() => signOut()}>
Sign Out
</button>
</>
)}
</li>
</ul>
</nav>
)
}الآن يمكننا تسجيل الدخول باستخدام Google وتسجيل الخروج وقراءة معلومات المستخدم الحالية!
إضافة Prisma
ابدأ بتثبيت prisma:
POWERSHELLnpm install prismaتهيئة Prisma مع:
POWERSHELLnpx prisma initهذا سوف ينشئ مجلد prisma مع ملف schema.prisma 👇
./prisma/schema.prismaTSX/* ./prisma/schema.prisma */
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}وسوف يقوم بإنشاء ملف env مع Connection String لقاعدة البيانات بأسم DATABASE_URL.
قاعدة بيانات PostgreSQL
يمكنك استخدام أي قاعدة بيانات تدعمها Prisma
سأقوم بإنشاء قاعدة بيانات PostgreSQL محلية تسمى prisma_blog مع psql 👇
POWERSHELL# تسجيل الدخول لقاعدة البيانات بأستخدام اسم المستخدم postgres 👇
psql -U postgres
# أنشاء قاعدة بيانات جديدة بأسم prisma_blog 👇
create database prisma_blog;نحتاج إلى Connection String حتى نتمكن من استخدام قاعدة البيانات المحددة هذه مع Prisma 👇
POWERSHELL# مثال للConnection String 👇
postgres://YourUserName:YourPassword@YourHostname:5432/YourDatabaseName
# للأتصال بقاعدة البيانات المحلية prisma_blog
# أنا لا أستخدم كلمة مرور ولكن تأكد من ادراجها إذا لزم الأمر
postgresql://postgres@localhost:5432/prisma_blog
يمكننا الآن استبدال الConnection String الموجودة تحت متغير البيئة DATABASE_URL إلى القيمة الصحيحة.
.envPOWERSHELL# .env
DATABASE_URL="postgresql://postgres@localhost:5432/prisma_blog"أستخدام Prisma Adapter في NextAuth
أبدء بتثبيت Prisma Adapter تحت أسم @next-auth/prisma-adapter:
POWERSHELLnpm install @next-auth/prisma-adapterأضف Prisma Adapter إلى [...nextauth].ts:
[...nextauth].tsTSX...
import { PrismaClient } from '@prisma/client'
import { PrismaAdapter } from '@next-auth/prisma-adapter'
const prisma = new PrismaClient()
export const authOptions = {
adapter: PrismaAdapter(prisma),
...
}نماذج Prisma (Models)
هناك فائدتان رئيسيتان لنماذج Prisma:
- تمثيل الجداول الفعلية في قاعدة البيانات ( PostgreSQL ).
- إضافة الأساس لـ Prisma Client API ( المستخدم للتواصل مع قاعدة البيانات. )
علينا إضافة العديد من النماذج لربط قاعدة البيانات الخاصة بنا بـ NextAuth وإضافة المنشورات لاحقًا.
جميع النماذج الضرورية:
./prisma/schema.prismaTSX/* ./prisma/schema.prisma */
...
model User {
id String @id @default(cuid())
name String?
username String?
password String?
email String @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
posts Post[]
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
}لدينا العديد من النماذج التي تم إنشاؤها هنا كل منها مفيد في جلستنا وإنشاء المنشورات:
- المستخدم (User) - يستخدم هذا الجدول لحفظ معلومات المستخدم الجديدة باستخدام معرف فريد وبيانات اعتماد المستخدم الأخرى إذا كانت متوفرة ويمكن أن يكون للمنشورات التي أنشأها هذا المستخدم.
- رمز التحقيق (Verification Token)
- الحسابات (Account) - يستخدم لحفظ الأنواع المختلفة من الحسابات للمستخدمين الذين سجلوا في تطبيقك لأنه يمكن لنفس المستخدم أن يسجل بالطريقة التقليدية (اسم المستخدم وكلمة السر) وبأستخدام حسابات OAuth اخرى مثل (Google, GitHub, الى أخره)
- الجلسة (Session) - جدول لجميع الجلسات الحالية المستخدمة مع المستخدمين في تطبيقك بمعرفهم الفريد ووقت انتهاء الصلاحية ومعرف المستخدم و رمز الجلسة Session Token التي يستخدمها المستخدمون لاستمرار الجلسة في التطبيق وطلب المعلومات من قاعدة البيانات.
- المنشورات Post - هذا الجدول مخصص للمعلومات المطلوبة للمنشور مثل العنوان والمؤلف ومحتوى المنشور.
تهجير نماذج Prisma (Migrate)
باستخدام Prisma Migrate ، يمكننا إنشاء جداول PostgreSQL الفعلية وفقًا للنماذج التي تم إنشاؤها أعلاه.
توليد Prisma Migrate:
POWERSHELLnpx prisma migrate dev- أدخل
initلاسم التهجير لتوضيح أن هذا هو أول تهجير لقاعدة البيانات.
سوف اقوم بالعودة لPSQL والبحث عن معلومات الجدوال 👇
pages/api/auth/[...nextauth].tsTSX/* pages/api/auth/[...nextauth].ts */
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
export const authOptions = {
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID || '',
clientSecret: process.env.GOOGLE_SECRET || '',
}),
// ...add more providers here
],
}
export default NextAuth(authOptions)سترى أيضًا مجلدًا جديدًا ضمن مجلد prisma يسمى التهجير (Migrate) والذي يحتوي على أكواد SQL لجميع عمليات التهجير التي قمت بها في مشروعك.
استخدام Prisma في التطبيق (Prisma Client)
قم بإنشاء ملف جديد داخل مجلد prisma باسم client.ts:
./prisma/client.tsTSX/* ./prisma/client.ts */
import { PrismaClient } from '@prisma/client'
// PrismaClient is attached to the `global` object in development to prevent
// exhausting your database connection limit.
//
// Learn more:
// https://pris.ly/d/help/next-js-best-practices
const globalForPrisma = global as unknown as { prisma: PrismaClient }
export const prisma = globalForPrisma.prisma || new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
export default prismaيستخدم هذا لمنع استنفاد حد الاتصال الأقصى بقاعدة البيانات.
أستديو Prisma
استخدم هذا الأمر لتشغيل أستديو Prisma:
POWERSHELLnpx prisma studioالآن إذا قمت بتسجيل الدخول باستخدام Google سترى مستخدمًا جديدًا ظهر في قاعدة البيانات!
صحفات التطبيق ومسارات الAPI
مسار الAPI للمنشورات (Post)
للتفاعل مع قاعدة بيانات PostgreSQL باستخدام Prisma ، يمكننا جلب البيانات باستخدام getStaticProps أو getServerSideProps أو باستخدام ``مسارات API
في هذا المنشور ، سأستخدم مزيجًا من getServerSideProps و ``API
احرص على عدم جلب البيانات من مسار الAPI في getServerSideProps لأنها قد تؤدي إلى أخطاء في السيرفر في الProduction.إنشاء مسار Post API مع هذا الكود
./api/post/index.tsTSX/* ./api/post/index.ts */
import type { NextApiRequest, NextApiResponse } from 'next'
import { prisma } from './../../../prisma/client'
import { getServerSession } from 'next-auth'
import { authOptions } from '../auth/[...nextauth]'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === 'GET') {}
if (req.method === 'POST') {}
if (req.method === 'PATCH') {}
if (req.method === 'DELETE') {}
}نحتاج إلى getServerSession لمعرفة كيف إذا كان هناك مستخدم قام بتسجيل الدخول أم لا لتحديث منشور أو إنشاء منشور جديد.
قراءة المنشورات ( GET )
لقراءة جميع المنشورات في قاعدة البيانات ، يمكننا استخدام استعلام findMany مع الwhere لتحديد فقط المنشورات الي تم نشرها من المؤلف.
TSXif (req.method === 'GET') {
try {
const data = await prisma.post.findMany({
where: {
published: true,
},
})
return res.status(200).json(data)
} catch (err) {
return res.status(500).json(err)
}
}نشر منشور ( POST )
لإضافة منشور جديد ، نحتاج أولاً إلى التحقق من جلسة المستخدم واستخدام بريدهم الإلكتروني لإنشاء ( INSERT ) منشور جديد في قاعدة البيانات.
TSXif (req.method === 'POST') {
const { title, content } = req.body
const session = await getServerSession(req, res, authOptions)
if (session) {
try {
const result = await prisma.post.create({
data: {
title,
content,
author: { connect: { email: session?.user?.email } },
},
})
res.json(result)
} catch (err) {
return res.status(500).json(err)
}
} else {
res.status(401).send({ message: 'Unauthorized' })
}
}تحديث منشور ( PATCH )
لتحديث حالة المنشور ، نحتاج إلى إرسال معرف المنشور وحالة المنشور هل هيا published او لا مع الطلب ثم تحديث المنشور ليتم نشره أم لا.
TSXif (req.method === 'PATCH') {
const { id, published } = req.body
if (!id || published === '') {
return res.status(401).send({ message: 'Unauthorized' })
}
const session = await getServerSession(req, res, authOptions)
if (session) {
try {
const result = await prisma.post.update({
where: { id },
data: { published: !published },
})
res.json(result)
} catch (err) {
return res.status(500).json(err)
}
} else {
res.status(401).send({ message: 'Unauthorized' })
}
}أنا لا أقوم بتحديث عنوان أو محتوى المنشور هنا ولكن يمكنك أضافته هنا إذا كنت تريد!
حذف المنشور ( DELETE )
TSXif (req.method === 'DELETE') {
const { id } = req.query.id
if (!id) res.status(401).send({ message: 'ID not found' })
const session = await getServerSession(req, res, authOptions)
if (session) {
try {
await prisma.post.delete({
where: { id: Number(id) },
})
res.status(200).send('Delete Post Successfully')
} catch (err) {
return res.status(500).json(err)
}
}
else {
res.status(401).send({ message: 'Unauthorized' })
}
}الصفحة الرئيسية
سأستخدم هوك useSWR من SWR بواسطة Vercel لجلب بيانات المنشورات في جهة العميل (Client Side)
يمكنك استخدام getServerSideProps مباشرة واستخدام عميل prisma للاستعلام عن بيانات النشر بدلاً من هذا النهج ، سيبدو مثل النهج في صفحة المسودات Draft في القسم التاليتثبيت SWR
POWERSHELLnpm install swrindex.tsxTSXimport { Post } from "@prisma/client"
import { useSession } from 'next-auth/react'
import Link from 'next/link'
import useSWR, { Fetcher } from 'swr'
const fetcher: Fetcher<any, string> = (...args) =>
fetch(...args).then((res) => res.json())
export default function Home() {
const { data: session } = useSession()
const { data: posts, isLoading, error } = useSWR('/api/post', fetcher)
if (error)
return (
<div className='w-full p-5'>
<h1>No Posts 🥲</h1>
</div>
)
if (isLoading) return <div className='w-full p-5'>Loading...</div>
return (
<div className='w-full p-5'>
{posts.map((post: Post) => (
<div key={post.id} className='py-5 border-b border-teal-400 px-5'>
<h1 className='font-bold text-2xl'>{post.title}</h1>
<p className='text-lg mt-4'>{post.content}</p>
{session?.user && post.authorId === session.user?.id && (
<Link href='/draft' className='btn inline-block mt-4'>
Edit
</Link>
)}
</div>
))}
</div>
)
}لم يتم أضافة معرف المستخدم (User ID) في الجلسة Session ، لذلك من أجل تضمينه ، سنستخدم دالة Session Callback لتمديد المعلومات المرسلة في الجلسة Session وإضافة معرف المستخدم 👇
pages/api/auth/[...nextauth].tsTSX/* pages/api/auth/[...nextauth].ts */
const prisma = new PrismaClient()
export const authOptions = {
adapter: PrismaAdapter(prisma),
providers: [...],
callbacks: {
async session({ session, user }: { session: Session; user: AdapterUser }) {
session.user.id = user.id
return session
},
},
}صفحة المسودات
draft.tsxTSXimport { GetServerSidePropsContext } from 'next'
import { signIn, useSession } from 'next-auth/react'
import prisma from '../prisma/client'
import Router from 'next/router'
import { Post } from '@prisma/client'
import { authOptions } from './api/auth/[...nextauth]'
import { getServerSession } from 'next-auth'
export async function getServerSideProps({
req,
res,
}: GetServerSidePropsContext) {
const session = await getServerSession(req, res, authOptions)
if (!session) {
return { props: { drafts: [] } }
}
const drafts = await prisma.post.findMany({
where: { author: { email: session.user?.email } },
})
return {
props: {
drafts,
},
}
}
export default function Draft({ drafts }: { drafts: Post[] }) {
const { data: session } = useSession()
if (!session) {
return (
<button className='btn text-lg mx-auto' onClick={() => signIn('google')}>
Sign In to see Drafts
</button>
)
}
async function handlePost(
e: React.SyntheticEvent,
id: number,
published: boolean,
del = false
) {
e.preventDefault()
try {
await fetch(`/api/post${del ? `?id=${id}` : ''}`, {
method: del ? 'DELETE' : 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: del ? '' : JSON.stringify({ id, published }),
})
await Router.push('/draft')
} catch (err) {
console.error(err)
}
}
return (
<div className='p-5 w-full'>
{drafts.map((draft: any) => (
<div
key={draft.title}
className='flex justify-between items-center border-b-2 pb-10 px-5'
>
<div>
<h2 className='text-2xl font-bold text-teal-600 my-4'>
{draft.title}
</h2>
<p>{draft.content}</p>
{!draft.published ? (
<button
onClick={(e) => handlePost(e, draft.id, draft.published)}
className='btn mt-4'
>
Publish
</button>
) : (
<button
onClick={(e) => handlePost(e, draft.id, draft.published)}
className='btn btn-red mt-4'
>
Unpublish
</button>
)}
</div>
<button
onClick={(e) => handlePost(e, draft.id, draft.published, true)}
className='btn btn-red'
>
Delete
</button>
</div>
))}
</div>
)
}صفحة المنشورات
TSXimport { signIn, useSession } from 'next-auth/react'
import Router from 'next/router'
import { useState } from 'react'
export default function Post() {
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const { data: session } = useSession()
if (!session) {
return (
<button className='btn mx-auto text-lg' onClick={() => signIn('google')}>
Sign In to create Posts
</button>
)
}
async function handleSubmit(e: React.SyntheticEvent) {
e.preventDefault()
if (!title || !content) return
try {
const body = { title, content }
const post = await fetch(`/api/post`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
if (post.ok) await Router.push('/draft')
throw new Error(post.statusText)
} catch (err) {
console.error(err)
}
}
return (
<form
onSubmit={handleSubmit}
className='flex flex-col gap-5 max-w-md w-full mx-auto justify-center'
>
<h1 className='text-xl font-bold text-teal-600 text-center'>
Create New Draft
</h1>
<label htmlFor='title'>Title</label>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
type='text'
id='title'
autoFocus
required
className='border p-2 text-black'
/>
<label htmlFor='content'>Content</label>
<textarea
value={content}
required
onChange={(e) => setContent(e.target.value)}
id='content'
className='border p-2 text-black'
/>
<button className='btn'>
Add Post
</button>
</form>
)
}الخاتمة
تمكنا من إنشاء مدونة باستخدام NextJS و NextAuth (مصادقة المستخدم) و Prisma ( قاعدة البيانات ) 😲
يمكن استخدام هذه الأدوات الثلاثة لإنشاء مشاريع Fullstack مع مصادقة وقاعدة بيانات آمنة!
NextJS
لقد استخدمت NextJS لفترة من الوقت ومن المميز في هذه الأداه انها لا تفشل في تقديم تجربة تطوير رائعة بالنسبة لي لإنشاء تطبيق جاهز للإنتاج Production مع أنواع مختلفة من طرق العرض مثل 👇
- SSR (Server Side Rendering)
- CSR (Client Side Rendering)
- SSG (Static Site Generation)
- ISR (Incremental Static Regeneration)
والقدرة على إنشاء واجهات برمجة تطبيقات مخصصة APIs ، مكون صورة مخصص Image Component ومجموعة من الخطافات والأساليب المفيدة وسهلة الاستخدام والفهم!
يمكنك قراءة معلومات عن طرق التقديم Render Methods من هنا.
NextAuth
المصادقة بNextAuth اعتقد انها أسهل طريقة لإنشاء مصادقة لتطبيق NextJS. لأنه سهل الاستخدام ومرن وآمن ويدعم أنواعًا متعددة من استراتيجيات التشفير ومحولات قاعدة البيانات!
NextAuth تنتقل لتصبح أداة جديدة تسمى الآن Auth.js والتي سيتم استخدامها مع أطر مختلفة أخرى غيرNextJS مثل SavelteKit و SolidJS
Prisma
تقدم Prisma تجربة رائعة للمطورين للتفاعل مع قواعد البيانات وتعديلها أو اختيار بين خيارات متعددة لقواعد البيانات.






