در دنیای توسعه وب مدرن، آموزش Next.js و کار با دادههای خارجی و GraphQL در Next.js از اهمیت ویژهای برخوردار است. این مقاله آموزشی جامع و کامل به شما کمک میکند تا از سطح مبتدی تا پیشرفته با این مفاهیم آشنا شوید و بتوانید پروژههای خود را به بهترین شکل مدیریت کنید. در این مقاله، تمامی جنبههای کار با دادههای خارجی و GraphQL در Next.js را پوشش میدهیم تا شما بتوانید با اطمینان و دانش کافی این فناوریها را به کار بگیرید.
اتصال به APIهای خارجی
یکی از اصول اساسی در توسعه وب، کار با دادههای خارجی و GraphQL در Next.js است. این بخش به شما نشان میدهد چگونه میتوانید به APIهای خارجی متصل شوید، دادهها را مدیریت کنید و از احراز هویت برای دسترسی به منابع محافظتشده استفاده کنید.
استفاده از REST APIs با Axios یا Fetch
برای کار با دادههای خارجی و GraphQL در Next.js، اولین قدم اتصال به APIهای خارجی است. دو روش محبوب برای این کار استفاده از Axios و Fetch میباشد. هر دوی این ابزارها امکان ارسال درخواستهای HTTP را فراهم میکنند، اما تفاوتهایی نیز دارند که در ادامه بررسی میکنیم.
Axios
Axios یک کتابخانه قدرتمند برای ارسال درخواستهای HTTP است که امکانات بیشتری نسبت به Fetch ارائه میدهد. از جمله مزایای Axios میتوان به پشتیبانی خودکار از JSON، مدیریت بهتر خطاها، و قابلیت تنظیم پیشفرضها اشاره کرد.
نصب Axios:
npm install axios
مثال با Axios:
import axios from 'axios';
const fetchData = async () => {
try {
const response = await axios.get('https://api.example.com/data');
console.log(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
Fetch
Fetch یک API بومی جاوااسکریپت برای ارسال درخواستهای HTTP است که در مرورگرها و محیطهای Node.js قابل استفاده است. Fetch به صورت پیشفرض در مرورگرها موجود است و نیازی به نصب کتابخانه اضافی ندارد.
مثال با Fetch:
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
مدیریت دادهها و خطاها
در کار با دادههای خارجی و GraphQL در Next.js، مدیریت دادهها و خطاها بسیار حیاتی است. این امر تضمین میکند که اپلیکیشن شما به درستی با مشکلات احتمالی برخورد میکند و تجربه کاربری مناسبی را فراهم میکند.
استفاده از بلوکهای try-catch
استفاده از بلوکهای try-catch برای مدیریت خطاها و بررسی وضعیت پاسخها از روشهای موثر در مدیریت درخواستهای HTTP است.
مثال مدیریت خطاها با Fetch:
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`Error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fetching error:', error);
return null;
}
};
مثال مدیریت خطاها با Axios:
import axios from 'axios';
const fetchData = async () => {
try {
const response = await axios.get('https://api.example.com/data');
return response.data;
} catch (error) {
if (error.response) {
// سرور پاسخ غیر موفقی داده است
console.error('Error response:', error.response.data);
} else if (error.request) {
// درخواستی ارسال شده اما پاسخی دریافت نشده است
console.error('No response received:', error.request);
} else {
// خطایی در تنظیم درخواست رخ داده است
console.error('Error setting up request:', error.message);
}
return null;
}
};
نمایش خطا به کاربر
برای بهبود تجربه کاربری، بهتر است خطاها را به گونهای نمایش دهید که کاربر از وضعیت اپلیکیشن مطلع شود.
مثال نمایش خطا با React:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const DataComponent = () => {
const [data, setData] = useState(null);
const [error, setError] = useState('');
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('https://api.example.com/data');
setData(response.data);
} catch (err) {
setError('مشکلی در دریافت دادهها رخ داده است.');
console.error(err);
}
};
fetchData();
}, []);
if (error) {
return <div>{error}</div>;
}
if (!data) {
return <div>در حال بارگذاری...</div>;
}
return (
<div>
<h1>دادهها</h1>
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
export default DataComponent;
احراز هویت و ارسال توکنها به APIها
بسیاری از APIها نیاز به احراز هویت دارند تا اطمینان حاصل شود که تنها کاربران مجاز به دادهها دسترسی دارند. در کار با دادههای خارجی و GraphQL در Next.js، ارسال توکنهای احراز هویت به APIها از طریق هدرهای درخواست انجام میشود.
ارسال توکن با Axios
Axios امکان تنظیم هدرهای درخواست را به راحتی فراهم میکند. میتوانید توکنهای احراز هویت را به صورت دستی به هر درخواست اضافه کنید یا از interceptors برای افزودن هدرها به صورت خودکار استفاده کنید.
مثال ارسال توکن با Axios به صورت دستی:
import axios from 'axios';
const fetchData = async () => {
try {
const token = 'your-auth-token';
const response = await axios.get('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
} catch (error) {
console.error('Error fetching data:', error);
return null;
}
};
استفاده از Interceptors برای افزودن توکن به صورت خودکار:
import axios from 'axios';
// تنظیم یک instance سفارشی از Axios
const apiClient = axios.create({
baseURL: 'https://api.example.com',
});
// افزودن interceptor برای افزودن توکن به هدرهای درخواست
apiClient.interceptors.request.use(config => {
const token = 'your-auth-token'; // این میتواند از state مدیریت شود
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, error => {
return Promise.reject(error);
});
// استفاده از apiClient برای ارسال درخواستها
const fetchData = async () => {
try {
const response = await apiClient.get('/data');
return response.data;
} catch (error) {
console.error('Error fetching data:', error);
return null;
}
};
ارسال توکن با Fetch
با استفاده از Fetch نیز میتوانید توکنهای احراز هویت را به هدرهای درخواست اضافه کنید.
مثال ارسال توکن با Fetch:
const fetchData = async () => {
try {
const token = 'your-auth-token';
const response = await fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
return null;
}
};
مدیریت توکنها با Context API یا Redux
برای مدیریت توکنهای احراز هویت در اپلیکیشنهای بزرگتر، میتوانید از Context API یا Redux استفاده کنید تا توکنها به صورت مرکزی مدیریت شوند و در سراسر اپلیکیشن در دسترس باشند.
مثال ساده با Context API:
// AuthContext.js
import React, { createContext, useState } from 'react';
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [token, setToken] = useState('');
return (
<AuthContext.Provider value={{ token, setToken }}>
{children}
</AuthContext.Provider>
);
};
// استفاده از AuthContext در یک کامپوننت
import React, { useContext } from 'react';
import { AuthContext } from './AuthContext';
import axios from 'axios';
const DataComponent = () => {
const { token } = useContext(AuthContext);
const fetchData = async () => {
try {
const response = await axios.get('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});
console.log(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
// فراخوانی fetchData در useEffect یا رویدادهای دیگر
};
نکات امنیتی
محافظت از توکنها: توکنهای احراز هویت باید به صورت امن ذخیره شوند. استفاده از HTTP-only cookies به جای localStorage برای جلوگیری از دسترسی جاوااسکریپت به توکنها توصیه میشود.
بهروزرسانی توکنها: توکنهای منقضیشونده باید به صورت خودکار تمدید شوند تا تجربه کاربری روانی فراهم شود.
مدیریت دسترسیها: اطمینان حاصل کنید که فقط کاربران مجاز به دسترسی به منابع محافظتشده هستند و از نقشها و مجوزهای مناسب استفاده کنید.
نمونه پروژه کامل
برای درک بهتر نحوه کار با دادههای خارجی و GraphQL در Next.js، میتوانید یک پروژه نمونه ایجاد کنید که شامل مراحل زیر باشد:
ایجاد یک پروژه Next.js جدید:
npx create-next-app@latest my-nextjs-app cd my-nextjs-app
نصب Axios:
npm install axios
ایجاد یک Context برای مدیریت توکنها:
// context/AuthContext.js
import React, { createContext, useState } from 'react';
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [token, setToken] = useState('');
return (
<AuthContext.Provider value={{ token, setToken }}>
{children}
</AuthContext.Provider>
);
};
افزودن AuthProvider به اپلیکیشن:
// pages/_app.js
import '../styles/globals.css';
import { AuthProvider } from '../context/AuthContext';
function MyApp({ Component, pageProps }) {
return (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
);
}
export default MyApp;
ایجاد یک صفحه برای نمایش دادهها با استفاده از Axios:
// pages/index.js
import React, { useContext, useEffect, useState } from 'react';
import axios from 'axios';
import { AuthContext } from '../context/AuthContext';
const Home = () => {
const { token } = useContext(AuthContext);
const [data, setData] = useState([]);
const [error, setError] = useState('');
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});
setData(response.data);
} catch (err) {
setError('مشکلی در دریافت دادهها رخ داده است.');
console.error(err);
}
};
fetchData();
}, [token]);
if (error) {
return <div>{error}</div>;
}
if (!data.length) {
return <div>در حال بارگذاری...</div>;
}
return (
<div>
<h1>دادهها</h1>
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
export default Home;
این نمونه پروژه به شما کمک میکند تا درک بهتری از نحوه کار با دادههای خارجی و GraphQL در Next.js به دست آورید و بتوانید آن را در پروژههای خود پیادهسازی کنید.
استفاده از GraphQL با Next.js
معرفی GraphQL و مزایای آن نسبت به REST
GraphQL یک زبان پرسوجو برای APIها است که توسط فیسبوک توسعه یافته است. این زبان به توسعهدهندگان امکان میدهد تا به صورت دقیق دادههایی که نیاز دارند را درخواست کنند، به جای دریافت دادههای اضافی یا ناقص که ممکن است در روشهای سنتی مانند REST رخ دهد.
مزایای GraphQL نسبت به REST:
پرسوجوهای دقیق: با GraphQL، میتوانید فقط دادههایی که نیاز دارید را درخواست کنید. این امر باعث کاهش حجم دادههای منتقل شده و بهبود عملکرد میشود.
یک نقطه پایان (Single Endpoint): برخلاف REST که معمولاً چندین نقطه پایان برای منابع مختلف دارد، GraphQL از یک نقطه پایان استفاده میکند. این امر سادهسازی توسعه و نگهداری API را فراهم میکند.
پشتیبانی از عملیات پیچیده: GraphQL به راحتی میتواند عملیات پیچیده مانند جویشهای تو در تو (Nested Queries) را مدیریت کند، که در REST ممکن است نیاز به چندین درخواست HTTP باشد.
توسعهدهندگان کنترل بیشتری دارند: توسعهدهندگان میتوانند ساختار دادههای پاسخ را کنترل کنند و از دریافت دادههای اضافی جلوگیری کنند.
قابلیت اکتشاف خود (Self-documenting): اسکیماهای GraphQL به طور خودکار مستندات قابل اکتشافی را فراهم میکنند که به توسعهدهندگان کمک میکند تا بهتر با API کار کنند.
در کار با دادههای خارجی و GraphQL در Next.js، این مزایا باعث میشود که مدیریت دادهها به صورت بهینهتر و کارآمدتری انجام شود، که به بهبود تجربه کاربری و کاهش بار سرور کمک میکند.
پیادهسازی Apollo Client در Next.js
Apollo Client یکی از محبوبترین کتابخانهها برای استفاده از GraphQL در Next.js است. Apollo Client امکانات بسیاری مانند کشینگ، مدیریت وضعیت (State Management) و ارتباط با سرور GraphQL را فراهم میکند. در ادامه نحوه نصب و پیکربندی Apollo Client در Next.js را بررسی میکنیم.
نصب Apollo Client
برای استفاده از Apollo Client در پروژه Next.js خود، ابتدا باید آن را نصب کنید:
npm install @apollo/client graphql
این دستور @apollo/client و graphql را به عنوان وابستگیهای پروژه نصب میکند.
پیکربندی Apollo Client
پس از نصب، باید Apollo Client را پیکربندی کنید تا بتوانید به سرور GraphQL متصل شوید. این پیکربندی معمولاً در یک فایل جداگانه مانند apolloClient.js انجام میشود.
مثال پیکربندی Apollo Client:
// lib/apolloClient.js
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://your-graphql-endpoint.com/graphql', // آدرس سرور GraphQL خود را وارد کنید
cache: new InMemoryCache(),
});
export default client;
در این مثال:
uri: آدرس سرور GraphQL شما است که درخواستهای GraphQL به آن ارسال میشود.
cache: Apollo Client از کش حافظه برای بهینهسازی عملکرد استفاده میکند. InMemoryCache یکی از روشهای کشینگ پیشفرض است که توسط Apollo ارائه شده است.
استفاده از Apollo Provider در اپلیکیشن Next.js
برای اینکه Apollo Client در سراسر اپلیکیشن شما در دسترس باشد، باید آن را با استفاده از ApolloProvider در فایل _app.js فراهم کنید.
مثال افزودن ApolloProvider به اپلیکیشن:
// pages/_app.js
import '../styles/globals.css';
import { ApolloProvider } from '@apollo/client';
import client from '../lib/apolloClient';
function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
);
}
export default MyApp;
در اینجا، ApolloProvider به عنوان یک wrapper برای اپلیکیشن شما عمل میکند و Apollo Client را در دسترس تمام کامپوننتها قرار میدهد.
استفاده از getStaticProps و getServerSideProps با GraphQL
در کار با دادههای خارجی و GraphQL در Next.js، میتوان از توابع getStaticProps و getServerSideProps برای دریافت دادهها در زمان ساخت (Build Time) یا در زمان درخواست (Request Time) استفاده کرد. این توابع به شما امکان میدهند تا دادهها را قبل از رندرینگ صفحه دریافت کنید و آنها را به عنوان props به کامپوننتهای صفحه ارسال کنید.
استفاده از getStaticProps با GraphQL
تابع getStaticProps به شما اجازه میدهد تا دادهها را در زمان ساخت صفحه دریافت کنید. این روش برای صفحات ایستا (Static Pages) که دادهها به ندرت تغییر میکنند، مناسب است.
مثال با getStaticProps:
// pages/index.js
import { gql } from '@apollo/client';
import client from '../lib/apolloClient';
export const getStaticProps = async () => {
const { data } = await client.query({
query: gql`
query GetData {
items {
id
name
}
}
`,
});
return {
props: {
items: data.items,
},
revalidate: 60, // صفحه هر 60 ثانیه دوباره تولید میشود
};
};
const Home = ({ items }) => (
<div>
<h1>Items</h1>
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
export default Home;
در این مثال:
query: یک پرسوجوی GraphQL است که دادههای مورد نیاز را از سرور دریافت میکند.
revalidate: مشخص میکند که صفحه هر چند ثانیه یکبار باید دوباره تولید شود. این ویژگی به عنوان Incremental Static Regeneration شناخته میشود و امکان بهروزرسانی محتوا بدون نیاز به بازسازی کامل سایت را فراهم میکند.
استفاده از getServerSideProps با GraphQL
تابع getServerSideProps به شما اجازه میدهد تا دادهها را در زمان درخواست دریافت کنید. این روش برای صفحات دینامیک که دادهها به طور مداوم تغییر میکنند یا بر اساس درخواستهای کاربر متفاوت هستند، مناسب است.
مثال با getServerSideProps:
// pages/dashboard.js
import { gql } from '@apollo/client';
import client from '../lib/apolloClient';
export const getServerSideProps = async (context) => {
const { data } = await client.query({
query: gql`
query GetUserData($id: ID!) {
user(id: $id) {
id
name
email
}
}
`,
variables: { id: context.params.id },
});
return {
props: {
user: data.user,
},
};
};
const Dashboard = ({ user }) => (
<div>
<h1>Dashboard</h1>
<p>نام: {user.name}</p>
<p>ایمیل: {user.email}</p>
</div>
);
export default Dashboard;
در این مثال:
variables: مقادیر متغیرها برای پرسوجوی GraphQL را تعیین میکند. در اینجا، id از پارامترهای مسیر (context.params.id) گرفته میشود.
دادههای کاربر با استفاده از getServerSideProps دریافت شده و به عنوان props به کامپوننت صفحه ارسال میشود.
مدیریت کشینگ و استیت با Apollo
Apollo Client امکانات پیشرفتهای برای مدیریت کشینگ و وضعیت (State Management) دادهها فراهم میکند. این امکانات در کار با دادههای خارجی و GraphQL در Next.js به بهبود عملکرد و تجربه کاربری کمک میکنند. در این بخش به بررسی نحوه مدیریت کشینگ و استیت با Apollo Client میپردازیم.
مدیریت کشینگ
کشینگ به شما امکان میدهد تا دادههای دریافت شده از سرور را در حافظه موقت نگهداری کنید تا در درخواستهای بعدی نیازی به دریافت مجدد آنها نباشد. این امر میتواند عملکرد اپلیکیشن شما را بهبود بخشد و بار سرور را کاهش دهد.
مثال مدیریت کشینگ با Apollo Client:
// lib/apolloClient.js
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://your-graphql-endpoint.com/graphql',
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
items: {
// سیاستهای کشینگ برای فیلد items
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
}),
});
export default client;
در این مثال:
typePolicies: سیاستهای کشینگ را برای انواع مختلف دادهها تعریف میکند. در اینجا، سیاست کشینگ برای فیلد items تعریف شده است تا دادههای جدید به دادههای موجود اضافه شوند.
استفاده از کشینگ برای بهروزرسانی دادهها
Apollo Client به شما امکان میدهد تا دادهها را به صورت دستی در کش بروزرسانی کنید. این ویژگی برای مواردی که دادهها به صورت محلی تغییر میکنند (مانند ویرایش یا حذف) بسیار مفید است.
مثال بروزرسانی کش با Apollo Client:
import { gql, useMutation } from '@apollo/client';
const UPDATE_ITEM = gql`
mutation UpdateItem($id: ID!, $name: String!) {
updateItem(id: $id, name: $name) {
id
name
}
}
`;
const UpdateItemComponent = () => {
const [updateItem] = useMutation(UPDATE_ITEM, {
update(cache, { data: { updateItem } }) {
const existingItems = cache.readQuery({
query: GET_ITEMS,
});
const newItems = existingItems.items.map(item =>
item.id === updateItem.id ? updateItem : item
);
cache.writeQuery({
query: GET_ITEMS,
data: { items: newItems },
});
},
});
const handleUpdate = () => {
updateItem({ variables: { id: '1', name: 'New Name' } });
};
return <button onClick={handleUpdate}>Update Item</button>;
};
در این مثال:
useMutation: برای اجرای Mutation استفاده میشود.
update: پس از اجرای Mutation، کش Apollo Client بروزرسانی میشود تا دادههای جدید منعکس شوند.
مدیریت وضعیت (State Management)
Apollo Client همچنین میتواند برای مدیریت وضعیت محلی اپلیکیشن شما استفاده شود. این قابلیت به شما اجازه میدهد تا دادههای وضعیت را به همراه دادههای GraphQL مدیریت کنید.
مثال مدیریت وضعیت با Apollo Client:
// lib/apolloClient.js
import { ApolloClient, InMemoryCache, makeVar } from '@apollo/client';
// تعریف یک Reactive Variable برای مدیریت وضعیت محلی
export const isLoggedInVar = makeVar(false);
const client = new ApolloClient({
uri: 'https://your-graphql-endpoint.com/graphql',
cache: new InMemoryCache(),
typePolicies: {
Query: {
fields: {
isLoggedIn: {
read() {
return isLoggedInVar();
},
},
},
},
},
});
export default client;
// کامپوننت نمایش وضعیت ورود
import React from 'react';
import { useReactiveVar } from '@apollo/client';
import { isLoggedInVar } from '../lib/apolloClient';
const AuthStatus = () => {
const isLoggedIn = useReactiveVar(isLoggedInVar);
const toggleLogin = () => {
isLoggedInVar(!isLoggedIn);
};
return (
<div>
<p>وضعیت ورود: {isLoggedIn ? 'ورود کردهاید' : 'خارج شدهاید'}</p>
<button onClick={toggleLogin}>
{isLoggedIn ? 'خروج' : 'ورود'}
</button>
</div>
);
};
export default AuthStatus;
در این مثال:
makeVar: برای تعریف یک Reactive Variable استفاده شده است که وضعیت ورود را مدیریت میکند.
useReactiveVar: برای دسترسی به مقدار Reactive Variable در کامپوننتها استفاده میشود.
در این بخش، به معرفی GraphQL و مزایای آن نسبت به REST پرداختیم و نحوه پیادهسازی Apollo Client در Next.js را بررسی کردیم. همچنین، استفاده از توابع getStaticProps و getServerSideProps برای دریافت دادهها با GraphQL و مدیریت کشینگ و وضعیت با Apollo Client را مورد بررسی قرار دادیم. این ابزارها و روشها به شما کمک میکنند تا اپلیکیشنهای Next.js خود را به صورت بهینهتر و کارآمدتری توسعه دهید.
اتصال به پایگاههای داده
استفاده از ORMها مانند Prisma یا Sequelize
برای کار با دادههای خارجی و GraphQL در Next.js، استفاده از ORMها مانند Prisma یا Sequelize میتواند فرآیند اتصال به پایگاههای داده را سادهتر کند. این ابزارها به شما امکان میدهند تا با استفاده از مدلهای دادهای، عملیات پایگاه داده را به راحتی انجام دهید.
Prisma
Prisma یکی از ORMهای محبوب برای جاوااسکریپت و تایپاسکریپت است که با استفاده از اسکیماهای قابل تعریف، ارتباط با پایگاه دادهها را تسهیل میکند. Prisma به عنوان یک ابزار مدرن ORM، امکاناتی مانند تایپاسکریپتشده بودن، Migrationهای ساده و قابلیتهای پیشرفتهی جستجو را فراهم میکند.
نصب Prisma:
برای نصب Prisma و راهاندازی اولیه آن در پروژه Next.js خود، مراحل زیر را دنبال کنید:
npm install @prisma/client npx prisma init
این دستورات Prisma را نصب کرده و فایلهای اولیه پیکربندی را ایجاد میکنند. پس از اجرای این دستورات، پوشهای به نام prisma در ریشه پروژه شما ایجاد میشود که شامل فایل schema.prisma است.
پیادهسازی CRUD با Next.js و ORMها
با استفاده از ORMها، میتوانید عملیات CRUD (ایجاد، خواندن، بهروزرسانی، حذف) را در پروژههای Next.js به سادگی پیادهسازی کنید. در ادامه به مثالهای عملی با استفاده از Prisma میپردازیم.
مثال ایجاد داده با Prisma
در این مثال، یک API Route در Next.js ایجاد میکنیم که با استفاده از Prisma، یک رکورد جدید در پایگاه داده ایجاد میکند.
// pages/api/create.js
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default async function handler(req, res) {
if (req.method === 'POST') {
const { name } = req.body;
try {
const newItem = await prisma.item.create({
data: { name },
});
res.status(201).json(newItem);
} catch (error) {
res.status(500).json({ message: 'Error creating item', error: error.message });
}
} else {
res.status(405).json({ message: 'Method not allowed' });
}
}
در این مثال:
PrismaClient: برای ارتباط با پایگاه داده استفاده میشود.
handler: تابعی است که درخواستهای HTTP را مدیریت میکند. در اینجا، فقط درخواستهای POST مجاز هستند.
prisma.item.create: عملیات ایجاد یک رکورد جدید در جدول item را انجام میدهد.
مثال خواندن داده با Prisma
در این مثال، یک API Route ایجاد میکنیم که تمامی رکوردهای جدول item را بازیابی میکند.
// pages/api/read.js
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default async function handler(req, res) {
if (req.method === 'GET') {
try {
const items = await prisma.item.findMany();
res.status(200).json(items);
} catch (error) {
res.status(500).json({ message: 'Error fetching items', error: error.message });
}
} else {
res.status(405).json({ message: 'Method not allowed' });
}
}
در این مثال:
prisma.item.findMany(): تمامی رکوردهای جدول item را بازیابی میکند.
مثال بهروزرسانی داده با Prisma
در این مثال، یک API Route ایجاد میکنیم که یک رکورد خاص را بهروزرسانی میکند.
// pages/api/update.js
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default async function handler(req, res) {
if (req.method === 'PUT') {
const { id, name } = req.body;
try {
const updatedItem = await prisma.item.update({
where: { id: Number(id) },
data: { name },
});
res.status(200).json(updatedItem);
} catch (error) {
res.status(500).json({ message: 'Error updating item', error: error.message });
}
} else {
res.status(405).json({ message: 'Method not allowed' });
}
}
در این مثال:
prisma.item.update: عملیات بهروزرسانی یک رکورد در جدول item را انجام میدهد.
مثال حذف داده با Prisma
در این مثال، یک API Route ایجاد میکنیم که یک رکورد خاص را حذف میکند.
// pages/api/delete.js
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default async function handler(req, res) {
if (req.method === 'DELETE') {
const { id } = req.body;
try {
const deletedItem = await prisma.item.delete({
where: { id: Number(id) },
});
res.status(200).json(deletedItem);
} catch (error) {
res.status(500).json({ message: 'Error deleting item', error: error.message });
}
} else {
res.status(405).json({ message: 'Method not allowed' });
}
}
در این مثال:
prisma.item.delete: عملیات حذف یک رکورد در جدول item را انجام میدهد.
مدیریت مهاجراها (Migrations) و مدلهای دادهای
مدیریت مهاجراها و مدلهای دادهای با ORMها در کار با دادههای خارجی و GraphQL در Next.js بسیار ساده است. این فرآیند به شما کمک میکند تا تغییرات ساختاری پایگاه داده را به راحتی اعمال کنید.
تعریف مدلهای دادهای با Prisma
ابتدا باید مدلهای دادهای خود را در فایل schema.prisma تعریف کنید. این فایل معمولاً در مسیر prisma/schema.prisma قرار دارد.
مثال تعریف مدل با Prisma:
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Item {
id Int @id @default(autoincrement())
name String
}
در این مثال:
datasource db: اطلاعات اتصال به پایگاه داده را مشخص میکند. در اینجا از PostgreSQL استفاده شده است.
generator client: تنظیمات مربوط به تولید Prisma Client را مشخص میکند.
model Item: مدل دادهای Item با فیلدهای id و name تعریف شده است.
ایجاد مهاجراها با Prisma
پس از تعریف مدلها، باید مهاجراها را ایجاد و اعمال کنید تا تغییرات در پایگاه داده اعمال شوند.
ایجاد مهاجرا:
npx prisma migrate dev --name init
این دستور یک مهاجرا جدید با نام init ایجاد کرده و آن را به پایگاه داده اعمال میکند. همچنین، Prisma Client را بهروزرسانی میکند تا با تغییرات جدید هماهنگ باشد.
اجرای دستورات Prisma Client
بعد از اعمال مهاجراها، میتوانید از Prisma Client برای اجرای عملیات پایگاه داده استفاده کنید.
مثال خواندن داده با Prisma:
// pages/api/read.js
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default async function handler(req, res) {
if (req.method === 'GET') {
try {
const items = await prisma.item.findMany();
res.status(200).json(items);
} catch (error) {
res.status(500).json({ message: 'Error fetching items', error: error.message });
}
} else {
res.status(405).json({ message: 'Method not allowed' });
}
}
در این مثال:
prisma.item.findMany(): تمامی رکوردهای جدول item را بازیابی میکند.
نکات پیشرفته در استفاده از ORMها
بهینهسازی پرسوجوها: ORMها مانند Prisma امکان بهینهسازی پرسوجوها را فراهم میکنند تا عملکرد اپلیکیشن شما بهبود یابد. استفاده از فیلترها، مرتبسازی و محدودیتهای مناسب در پرسوجوها میتواند به کاهش بار سرور و افزایش سرعت پاسخدهی کمک کند.
مثال بهینهسازی پرسوجو با Prisma:
const getFilteredItems = async (filter) => {
try {
const items = await prisma.item.findMany({
where: {
name: {
contains: filter,
mode: 'insensitive',
},
},
orderBy: {
name: 'asc',
},
take: 10,
});
return items;
} catch (error) {
console.error('Error fetching filtered items:', error);
return [];
}
};
مدیریت ارتباطات پیچیده: با استفاده از ORMها میتوانید ارتباطات پیچیده بین مدلها را مدیریت کنید، مانند ارتباطات یک به چند یا چند به چند. این امر به شما امکان میدهد تا دادههای مرتبط را به صورت مؤثر مدیریت کنید.
مثال تعریف مدلهای مرتبط با Prisma:
// prisma/schema.prisma
model User {
id Int @id @default(autoincrement())
name String
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String
authorId Int
author User @relation(fields: [authorId], references: [id])
}
در این مثال، یک ارتباط یک به چند بین مدلهای User و Post تعریف شده است.
اعتبارسنجی دادهها: ORMها امکاناتی برای اعتبارسنجی دادهها قبل از ذخیرهسازی در پایگاه داده فراهم میکنند. این امر به جلوگیری از ورود دادههای نامعتبر و حفظ انسجام دادهها کمک میکند.
مثال اعتبارسنجی دادهها با Prisma Middleware:
// prisma/middleware.js
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
prisma.$use(async (params, next) => {
if (params.model === 'Item' && params.action === 'create') {
if (!params.args.data.name) {
throw new Error('Name is required');
}
}
return next(params);
});
export default prisma;
در این مثال، قبل از ایجاد یک رکورد جدید در جدول Item، وجود فیلد name بررسی میشود.
استفاده از Sequelize به جای Prisma
اگرچه Prisma یکی از محبوبترین ORMها است، اما Sequelize نیز یک گزینه قدرتمند برای مدیریت پایگاه دادهها در پروژههای Node.js و Next.js محسوب میشود. Sequelize امکانات مشابهی با Prisma ارائه میدهد، از جمله تعریف مدلها، مدیریت مهاجراها و اجرای عملیات CRUD.
نصب Sequelize
برای نصب Sequelize و راهاندازی اولیه آن در پروژه Next.js خود، مراحل زیر را دنبال کنید:
npm install sequelize sequelize-cli pg pg-hstore npx sequelize-cli init
این دستورات Sequelize و وابستگیهای مورد نیاز آن را نصب کرده و ساختار اولیه پروژه را ایجاد میکنند. پس از اجرای این دستورات، پوشههایی مانند models, migrations, و config در پروژه شما ایجاد میشوند.
تعریف مدل با Sequelize
ابتدا باید مدلهای دادهای خود را با استفاده از Sequelize تعریف کنید.
مثال تعریف مدل با Sequelize:
// models/item.js
module.exports = (sequelize, DataTypes) => {
const Item = sequelize.define('Item', {
name: {
type: DataTypes.STRING,
allowNull: false,
},
});
return Item;
};
در این مثال:
sequelize.define: مدل Item با فیلد name تعریف شده است.
allowNull: مشخص میکند که فیلد name نمیتواند خالی باشد.
ایجاد مهاجرا با Sequelize
پس از تعریف مدلها، باید مهاجراها را ایجاد و اعمال کنید تا تغییرات در پایگاه داده اعمال شوند.
ایجاد مهاجرا:
npx sequelize-cli model:generate --name Item --attributes name:string npx sequelize-cli db:migrate
این دستورات مدل Item را تعریف کرده و مهاجراهای لازم را ایجاد و اعمال میکنند. اولین دستور یک فایل مهاجرا ایجاد میکند که ساختار جدول Items را تعریف میکند و دومین دستور این مهاجراها را به پایگاه داده اعمال میکند.
مثال خواندن داده با Sequelize
در این مثال، یک API Route ایجاد میکنیم که تمامی رکوردهای جدول Item را بازیابی میکند.
// pages/api/read.js
import { Sequelize, DataTypes } from 'sequelize';
import sequelize from '../../config/database'; // فایل پیکربندی Sequelize
const Item = sequelize.define('Item', {
name: {
type: DataTypes.STRING,
allowNull: false,
},
}, {
tableName: 'Items',
});
export default async function handler(req, res) {
if (req.method === 'GET') {
try {
const items = await Item.findAll();
res.status(200).json(items);
} catch (error) {
res.status(500).json({ message: 'Error fetching items', error: error.message });
}
} else {
res.status(405).json({ message: 'Method not allowed' });
}
}
در این مثال:
sequelize.define: مدل Item را تعریف میکند.
Item.findAll(): تمامی رکوردهای جدول Items را بازیابی میکند.
نکات پیشرفته در استفاده از ORMها
بهینهسازی پرسوجوها: ORMها امکان بهینهسازی پرسوجوها را فراهم میکنند تا عملکرد اپلیکیشن شما بهبود یابد. استفاده از فیلترها، مرتبسازی و محدودیتهای مناسب در پرسوجوها میتواند به کاهش بار سرور و افزایش سرعت پاسخدهی کمک کند.
مثال بهینهسازی پرسوجو با Sequelize:
const getFilteredItems = async (filter) => {
try {
const items = await Item.findAll({
where: {
name: {
[Sequelize.Op.like]: `%${filter}%`,
},
},
order: [['name', 'ASC']],
limit: 10,
});
return items;
} catch (error) {
console.error('Error fetching filtered items:', error);
return [];
}
};
مدیریت ارتباطات پیچیده: با استفاده از ORMها میتوانید ارتباطات پیچیده بین مدلها را مدیریت کنید، مانند ارتباطات یک به چند یا چند به چند. این امر به شما امکان میدهد تا دادههای مرتبط را به صورت مؤثر مدیریت کنید.
مثال تعریف مدلهای مرتبط با Sequelize:
// models/user.js
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false,
},
});
User.associate = (models) => {
User.hasMany(models.Post, { foreignKey: 'userId' });
};
return User;
};
// models/post.js
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
title: {
type: DataTypes.STRING,
allowNull: false,
},
content: {
type: DataTypes.TEXT,
allowNull: false,
},
userId: {
type: DataTypes.INTEGER,
allowNull: false,
},
});
Post.associate = (models) => {
Post.belongsTo(models.User, { foreignKey: 'userId' });
};
return Post;
};
در این مثال، یک ارتباط یک به چند بین مدلهای User و Post تعریف شده است.
اعتبارسنجی دادهها: ORMها امکاناتی برای اعتبارسنجی دادهها قبل از ذخیرهسازی در پایگاه داده فراهم میکنند. این امر به جلوگیری از ورود دادههای نامعتبر و حفظ انسجام دادهها کمک میکند.
مثال اعتبارسنجی دادهها با Sequelize:
const Item = sequelize.define('Item', {
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: {
msg: 'Name must not be empty',
},
},
},
}, {
tableName: 'Items',
});
در این مثال، فیلد name باید خالی نباشد و اگر خالی باشد، یک پیام خطا نمایش داده میشود.
استفاده از Sequelize به جای Prisma
اگرچه Prisma یکی از محبوبترین ORMها است، اما Sequelize نیز یک گزینه قدرتمند برای مدیریت پایگاه دادهها در پروژههای Node.js و Next.js محسوب میشود. Sequelize امکانات مشابهی با Prisma ارائه میدهد، از جمله تعریف مدلها، مدیریت مهاجراها و اجرای عملیات CRUD.
نصب Sequelize
برای نصب Sequelize و راهاندازی اولیه آن در پروژه Next.js خود، مراحل زیر را دنبال کنید:
npm install sequelize sequelize-cli pg pg-hstore npx sequelize-cli init
این دستورات Sequelize و وابستگیهای مورد نیاز آن را نصب کرده و ساختار اولیه پروژه را ایجاد میکنند. پس از اجرای این دستورات، پوشههایی مانند models, migrations, و config در پروژه شما ایجاد میشوند.
تعریف مدل با Sequelize
ابتدا باید مدلهای دادهای خود را با استفاده از Sequelize تعریف کنید.
مثال تعریف مدل با Sequelize:
// models/item.js
module.exports = (sequelize, DataTypes) => {
const Item = sequelize.define('Item', {
name: {
type: DataTypes.STRING,
allowNull: false,
},
});
return Item;
};
در این مثال:
sequelize.define: مدل Item با فیلد name تعریف شده است.
allowNull: مشخص میکند که فیلد name نمیتواند خالی باشد.
ایجاد مهاجرا با Sequelize
پس از تعریف مدلها، باید مهاجراها را ایجاد و اعمال کنید تا تغییرات در پایگاه داده اعمال شوند.
ایجاد مهاجرا:
npx sequelize-cli model:generate --name Item --attributes name:string npx sequelize-cli db:migrate
این دستورات مدل Item را تعریف کرده و مهاجراهای لازم را ایجاد و اعمال میکنند. اولین دستور یک فایل مهاجرا ایجاد میکند که ساختار جدول Items را تعریف میکند و دومین دستور این مهاجراها را به پایگاه داده اعمال میکند.
مثال خواندن داده با Sequelize
در این مثال، یک API Route ایجاد میکنیم که تمامی رکوردهای جدول Item را بازیابی میکند.
// pages/api/read.js
import { Sequelize, DataTypes } from 'sequelize';
import sequelize from '../../config/database'; // فایل پیکربندی Sequelize
const Item = sequelize.define('Item', {
name: {
type: DataTypes.STRING,
allowNull: false,
},
}, {
tableName: 'Items',
});
export default async function handler(req, res) {
if (req.method === 'GET') {
try {
const items = await Item.findAll();
res.status(200).json(items);
} catch (error) {
res.status(500).json({ message: 'Error fetching items', error: error.message });
}
} else {
res.status(405).json({ message: 'Method not allowed' });
}
}
در این مثال:
sequelize.define: مدل Item را تعریف میکند.
Item.findAll(): تمامی رکوردهای جدول Items را بازیابی میکند.
نکات پیشرفته در استفاده از ORMها
بهینهسازی پرسوجوها: ORMها مانند Prisma و Sequelize امکان بهینهسازی پرسوجوها را فراهم میکنند تا عملکرد اپلیکیشن شما بهبود یابد. استفاده از فیلترها، مرتبسازی و محدودیتهای مناسب در پرسوجوها میتواند به کاهش بار سرور و افزایش سرعت پاسخدهی کمک کند.
مثال بهینهسازی پرسوجو با Sequelize:
const getFilteredItems = async (filter) => {
try {
const items = await Item.findAll({
where: {
name: {
[Sequelize.Op.like]: `%${filter}%`,
},
},
order: [['name', 'ASC']],
limit: 10,
});
return items;
} catch (error) {
console.error('Error fetching filtered items:', error);
return [];
}
};
مدیریت ارتباطات پیچیده: با استفاده از ORMها میتوانید ارتباطات پیچیده بین مدلها را مدیریت کنید، مانند ارتباطات یک به چند یا چند به چند. این امر به شما امکان میدهد تا دادههای مرتبط را به صورت مؤثر مدیریت کنید.
مثال تعریف مدلهای مرتبط با Sequelize:
// models/user.js
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false,
},
});
User.associate = (models) => {
User.hasMany(models.Post, { foreignKey: 'userId' });
};
return User;
};
// models/post.js
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
title: {
type: DataTypes.STRING,
allowNull: false,
},
content: {
type: DataTypes.TEXT,
allowNull: false,
},
userId: {
type: DataTypes.INTEGER,
allowNull: false,
},
});
Post.associate = (models) => {
Post.belongsTo(models.User, { foreignKey: 'userId' });
};
return Post;
};
در این مثال، یک ارتباط یک به چند بین مدلهای User و Post تعریف شده است.
اعتبارسنجی دادهها: ORMها امکاناتی برای اعتبارسنجی دادهها قبل از ذخیرهسازی در پایگاه داده فراهم میکنند. این امر به جلوگیری از ورود دادههای نامعتبر و حفظ انسجام دادهها کمک میکند.
مثال اعتبارسنجی دادهها با Sequelize:
const Item = sequelize.define('Item', {
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: {
msg: 'Name must not be empty',
},
},
},
}, {
tableName: 'Items',
});
در این مثال، فیلد name باید خالی نباشد و اگر خالی باشد، یک پیام خطا نمایش داده میشود.
در این بخش، به استفاده از ORMهایی مانند Prisma و Sequelize برای اتصال به پایگاههای داده در Next.js پرداختیم. همچنین نحوه پیادهسازی عملیات CRUD و مدیریت مهاجراها را بررسی کردیم. استفاده از ORMها به شما امکان میدهد تا به صورت موثر و کارآمد با پایگاههای داده خود ارتباط برقرار کنید و عملیاتهای پیچیده را به راحتی مدیریت نمایید.
نتیجهگیری
در این مقاله، به تفصیل به کار با دادههای خارجی و GraphQL در Next.js پرداختیم و ابزارها و روشهای مختلفی را برای بهینهسازی این فرآیندها معرفی کردیم. با استفاده از Axios و Fetch برای ارتباط با REST APIs و بهرهگیری از Apollo Client برای مدیریت GraphQL، توانستیم نحوه دریافت و مدیریت دادهها را به شکلی مؤثر و کارآمد پیادهسازی کنیم.
همچنین، با معرفی ORMهایی مانند Prisma و Sequelize، فرآیند اتصال به پایگاههای داده را سادهتر کرده و امکان اجرای عملیاتهای CRUD (ایجاد، خواندن، بهروزرسانی، حذف) را به صورت آسانتری فراهم آوردیم. این ORMها با ارائه قابلیتهای پیشرفته مانند مدیریت مهاجراها، تعریف مدلهای دادهای، و بهینهسازی پرسوجوها، نقش مهمی در افزایش کارایی و قابلیت نگهداری اپلیکیشنهای Next.js ایفا میکنند.
مزایای استفاده از GraphQL نسبت به REST در Next.js شامل کاهش تعداد درخواستها، دریافت دقیق دادههای مورد نیاز، و بهبود عملکرد اپلیکیشن است. Apollo Client با امکانات کشینگ و مدیریت وضعیت دادهها، تجربه کاربری بهتری را فراهم میکند و توسعهدهندگان را قادر میسازد تا اپلیکیشنهای مقیاسپذیر و کارآمدی را ایجاد کنند.
استفاده از ORMها مانند Prisma و Sequelize نه تنها ارتباط با پایگاههای داده را سادهتر میکند، بلکه با ارائه ابزارهایی برای مدیریت مهاجراها و مدلهای دادهای، توسعهدهندگان را در اجرای عملیاتهای پیچیدهتر یاری میدهد. این ابزارها با فراهم کردن امکان تعریف دقیق مدلهای دادهای و انجام عملیاتهای پایگاه داده به صورت برنامهنویسی شده، به بهبود روند توسعه و نگهداری اپلیکیشن کمک شایانی میکنند.
در نهایت، با ترکیب Next.js، GraphQL، Apollo Client، و ORMهای قدرتمند، میتوانید اپلیکیشنهای وب مدرنی را بسازید که نه تنها از نظر عملکردی بهینه هستند، بلکه از نظر ساختاری نیز قابل نگهداری و توسعهاند. توصیه میشود که با پیادهسازی پروژههای عملی و بهرهگیری از منابع معرفیشده، دانش خود را در این زمینه بهبود بخشیده و به توسعهدهندهای ماهر و کارآمد تبدیل شوید.
