الفرق بين المراجعتين لصفحة: «Next.js/api routes»

من موسوعة حسوب
لا ملخص تعديل
ط مراجعة
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:المسارات إلى الواجهة البرمجية API في Next.js}}</noinclude>
<noinclude>{{DISPLAYTITLE:مسارات الواجهة البرمجية API Routes في Next.js}}</noinclude>
تٌعد مسارات API طريقة لبناء الواجهة البرمجية لتطبيقك باستخدام Next.js. إذ يُربط كل ملف ضمن المجلد <code>pages/api</code> بالمسار <code>*/api/</code> ويُعامل كوصلة API (نقطة اتصال مع الواجهة البرمجية) بدلًا من كونه صفحة <code>page</code>. تمثّل هذه الوصلات تجمّعات bundles في جانب الخادم فقط ولن تزيد حجم تجميعة التطبيق من جانب العميل. يعيد مسار API التالي <code>pages/api/user.js</code> على سبيل المثال استجابة بصيغة <code>json</code> مع رمز الحالة <code>200</code>:<syntaxhighlight lang="javascript">
تٌعد مسارات الواجهة البرمجية API طريقة لبناء الواجهة البرمجية الخلفية لتطبيقك باستخدام Next.js الذي يعمل في هذه الحالة كواجهة أمامية frontend وواجهة خلفية backend. إذ يُربط كل ملف ضمن المجلد <code>pages/api</code> بالمسار <code>*/api/</code> ويُعامل كوصلة API (نقطة اتصال مع الواجهة البرمجية) بدلًا من كونه صفحة <code>page</code>. تمثّل هذه الوصلات تجمّعات bundles في جانب الخادم فقط ولن تزيد حجم تجميعة التطبيق من جانب العميل. يعيد مسار API التالي <code>pages/api/user.js</code> على سبيل المثال استجابة بصيغة <code>json</code> مع رمز الحالة <code>200</code>:<syntaxhighlight lang="javascript">
export default function handler(req, res) {
export default function handler(req, res) {
   res.status(200).json({ name: 'John Doe' })
   res.status(200).json({ name: 'John Doe' })
}
}
</syntaxhighlight>ولكي يعمل مسار API لا بدّ من تصدير دالة على أنها افتراضية <code>default</code> (مثل الدالة <code>handler</code> في المثال السابق) ومن ثم تستقبل المعاملات التالية:
</syntaxhighlight>'''ملاحظة''': مسارات API ستتأثر عبر ضبط <code>pageExtensions</code> في الملف [[Next.js/next.config.js|next.config.js]].


* <code>req</code>: يضم نسخة عن الكائن [[Node.js/http#.D8.A7.D9.84.D8.B5.D9.86.D9.81%20http.IncomingMessage|http.IncomingMessage]] بالإضافة إلى بعض الأدوات الوسطية المبنية مسبقًا (سنتعرف عليها لاحقًا في هذه الصفحة).
ولكي يعمل مسار API لا بدّ من تصدير دالة على أنها افتراضية <code>default</code> (مثل الدالة <code>handler</code> في المثال السابق) ومن ثم تستقبل المعاملات التالية:
* <code>res</code>: يضم نسخة عن الكائن [[Node.js/http#.D8.A7.D9.84.D8.B5.D9.86.D9.81%20http.ServerResponse|http.ServerResponse]] بالإضافة إلى دوال مساعدة (سنتعرف عليها لاحقًا في هذه الصفحة).
 
*<code>req</code>: يضم نسخة عن الكائن [[Node.js/http#.D8.A7.D9.84.D8.B5.D9.86.D9.81%20http.IncomingMessage|http.IncomingMessage]] بالإضافة إلى بعض الأدوات الوسطية middlewares المبنية مسبقًا (سنتعرف عليها لاحقًا في هذه الصفحة).
*<code>res</code>: يضم نسخة عن الكائن [[Node.js/http#.D8.A7.D9.84.D8.B5.D9.86.D9.81%20http.ServerResponse|http.ServerResponse]] بالإضافة إلى دوال مساعدة (سنتعرف عليها لاحقًا في هذه الصفحة).


يمكنك استخدام الكائن <code>req.method</code> ضمن معالج الطلب request handler إن أردت التعامل مع الأنواع المختلفة لطلبات HTTP في مسار API. إليك مثالًا:<syntaxhighlight lang="javascript">
يمكنك استخدام الكائن <code>req.method</code> ضمن معالج الطلب request handler إن أردت التعامل مع الأنواع المختلفة لطلبات HTTP في مسار API. إليك مثالًا:<syntaxhighlight lang="javascript">
سطر 20: سطر 22:


=== حالات استخدام مسارت API في Next.js ===
=== حالات استخدام مسارت API في Next.js ===
يمكنك بناء الواجهة البرمجية API بأكملها لمشروعك الجديد باستخدام مسارات API. فإن كان لديك واجهة برمجية جاهزة، لا حاجة عندها لتوجيه الاستدعاءات إليها من خلال مسارات API. إليك بعض الحالات التي يمكن استخدام مسارات API فيها:
يمكنك بناء الواجهة البرمجية الخلفية API بأكملها لمشروعك الجديد باستخدام مسارات API. فإن كان لديك واجهة برمجية جاهزة، لا حاجة عندها لتوجيه الاستدعاءات إليها من خلال مسارات API. إليك بعض الحالات التي يمكن استخدام مسارات API فيها:


* إخفاء عنوان URL عن خدمة خارجية (مثال: <code>api/secret/</code> بدلًا عن <code><nowiki>https://company.com/secret-url</nowiki></code>)
* إخفاء عنوان URL عن خدمة خارجية (مثال: <code>api/secret/</code> بدلًا عن <code><nowiki>https://company.com/secret-url</nowiki></code>)
* استخدام [[Next.js/environment variables|متغيرات بيئة]] ضمن الخادم للولوج الآمن إلى خدمات خارجية.
* استخدام [[Next.js/environment variables|متغيرات بيئة]] ضمن الخادم للولوج الآمن إلى خدمات خارجية.


=== التحفظات على استخدام مسارت API في Next.js ===
=== محاذير ===


* لا تحدد مسارات API ترويسات مشاركة الموارد ذات الأصل المختلط CORS، أي أنها افتراضيًا للموارد ذات الأصل المشترك same-origin فقط. يمكنك التحكم بهذا السلوك عبر تغليف معالج الطلب باستخدام أداة CORS وسطية.
* لا تحدد مسارات API ترويسات مشاركة الموارد ذات الأصل المختلط CORS، أي أنها افتراضيًا للموارد ذات الأصل المشترك same-origin فقط. يمكنك التحكم بهذا السلوك عبر [https://github.com/vercel/next.js/tree/canary/examples/api-routes-cors تغليف معالج الطلب باستخدام أداة CORS وسطية].
* لا يمكن استخدام مسارات API مع [[Next.js/static html export|<code>next export</code>]].
* لا يمكن استخدام مسارات API مع [[Next.js/static html export|<code>next export</code>]].


== مسارات API ديناميكية ==
== مسارات API ديناميكية ==
تدعم مسارات API [[Next.js/Routing|الوجهات الديناميكية]] وتخضع لجميع قواعد تسمية الملفات التي تستخدم مع الصفحات <code>pages</code>. فشيفرة مسار API للوجهة <code>pages/api/post/[pid].js</code> هي كالتالي:<syntaxhighlight lang="javascript">
تدعم مسارات API [[Next.js/Routing|الوجهات الديناميكية]] dynamic routes وتخضع لجميع قواعد تسمية الملفات التي تستخدم مع الصفحات <code>pages</code>. فشيفرة مسار API للوجهة <code>pages/api/post/[pid].js</code> هي كالتالي:<syntaxhighlight lang="javascript">
export default function handler(req, res) {
export default function handler(req, res) {
   const { pid } = req.query
   const { pid } = req.query
   res.end(`Post: ${pid}`)
   res.end(`Post: ${pid}`)
}
}
</syntaxhighlight>
</syntaxhighlight>فإن أرسل طلب إلى <code>api/post/abc/</code> فسيكون الرد نصًا هو <code>Post: abc</code>.


=== الصفحة index ومسارات API الديناميكية ===
=== الصفحة index ومسارات API الديناميكية ===
سطر 53: سطر 55:
** <code>api/posts/[postId].js/</code>
** <code>api/posts/[postId].js/</code>


إنّ النموذجين السابقين متكافئين. وهنالك خيار ثالث يستخدم فقط النموذج <code>api/posts/[postId].js/</code> لكنه لا يصلح لحالتنا لأنّ المسارات الديناميكية (بما فيها التقاط جميع المسارات Catch-all routes) لا تمتلك حالة غير محددة <code>undefined</code> وبالتالي لن يتمكن <code>GET api/posts</code> من مطابقة الوجهة <code>api/posts/[postId].js/</code> أبدًا.
إنّ النموذجين السابقين متكافئين. وهنالك خيار ثالث يستخدم فقط النموذج <code>api/posts/[postId].js/</code> لكنه لا يصلح لأنّ المسارات الديناميكية (بما فيها التقاط جميع المسارات Catch-all routes) لا تمتلك حالة غير محددة <code>undefined</code> وبالتالي لن يتمكن <code>GET api/posts</code> من مطابقة الوجهة <code>api/posts/[postId].js/</code> أبدًا.


=== التقاط جميع الوجهات من خلال مسار API الديناميكي ===
=== التقاط جميع الوجهات من خلال مسار API الديناميكي ===
بالإمكان توسعة مسارات API الديناميكية لمطابقة كل المسارات وذلك بإضافة ثلاث نقاط (<code>...</code>) داخل القوسين المربعين. إليك مثالًا:
بالإمكان توسعة مسارات API الديناميكية لمطابقة كل المسارات وذلك بإضافة ثلاث نقاط <code>...</code> داخل القوسين المربعين. إليك مثالًا:


* تطابق الوجهة <code>pages/api/post/[...slug].js</code> المسار <code>api/post/a/</code> كما تطابق <code>api/post/a/b/</code> و <code>api/post/a/b/c/</code> وهكذا. بإمكانك طبعًا اختيار أي اسم بدلًا من <code>slug</code> مثل <code>[param...]</code>.
* تطابق الوجهة <code>pages/api/post/[...slug].js</code> المسار <code>api/post/a/</code> كما تطابق <code>api/post/a/b/</code> و <code>api/post/a/b/c/</code> وهكذا. بإمكانك طبعًا اختيار أي اسم بدلًا من <code>slug</code> مثل <code>[param...]</code>.
سطر 62: سطر 64:
تُرسل معاملات البحث كمعاملات استعلام (<code>slug</code> مثال على ذلك) إلى الصفحة، وتشكل المعاملات دائمًا مصفوفة. وبالتالي سيكون كائن الاستعلام عن المسار <code>api/post/a/</code> مثلًا هو:<syntaxhighlight lang="json">
تُرسل معاملات البحث كمعاملات استعلام (<code>slug</code> مثال على ذلك) إلى الصفحة، وتشكل المعاملات دائمًا مصفوفة. وبالتالي سيكون كائن الاستعلام عن المسار <code>api/post/a/</code> مثلًا هو:<syntaxhighlight lang="json">
{ "slug": ["a"] }
{ "slug": ["a"] }
</syntaxhighlight>في حالة <code>api/post/a/b/</code> وأية مسارات مطابقة أخرى، ستُضاف البارمترات الجديدة إلى المصفوفة كالتالي:<syntaxhighlight lang="json">
</syntaxhighlight>في حالة <code>api/post/a/b/</code> وأية مسارات مطابقة أخرى، ستُضاف المعاملات الجديدة إلى المصفوفة كالتالي:<syntaxhighlight lang="json">
{ "slug": ["a", "b"] }
{ "slug": ["a", "b"] }
</syntaxhighlight>يبدو مسار API إلى الوجهة <code>pages/api/post/[...slug].js</code> كالتالي:<syntaxhighlight lang="javascript">
</syntaxhighlight>يبدو مسار API إلى الوجهة <code>pages/api/post/[...slug].js</code> كالتالي:<syntaxhighlight lang="javascript">
سطر 72: سطر 74:


=== التقاط جميع الوجهات اختياريًا ===
=== التقاط جميع الوجهات اختياريًا ===
يمكن التقاط جميع الوجهات اختياريًا بإضافة المعامل بين قوسين مربعين مزدوجين مثل (<code><nowiki>[[...slug]]</nowiki></code>). إذ ستطابق الوجهة <code>pages/api/post/<nowiki>[[...slug]]</nowiki>.js</code> مثلًا المسارات <code>api/post/</code> و <code>api/post/a/</code> و <code>api/post/a/b/</code> وهكذا.
يمكن التقاط جميع الوجهات اختياريًا بإضافة المعامل بين قوسين مربعين مزدوجين مثل <code><nowiki>[[...slug]]</nowiki></code> إذ ستطابق الوجهة <code>pages/api/post/<nowiki>[[...slug]]</nowiki>.js</code> مثلًا المسارات <code>api/post/</code> و <code>api/post/a/</code> و <code>api/post/a/b/</code> وهكذا.


إنّ الاختلاف الرئيسي بين التقاط جميع الوجهات والتقاط جميع الوجهات اختياريًا هو أنّ الالتقاط الاختياري سيطابق أيضًا المسارات التي لا تضم معامل البحث (المسار <code>api/post/</code> مثالًا).  
إنّ الاختلاف الرئيسي بين التقاط جميع الوجهات والتقاط جميع الوجهات اختياريًا هو أنّ الالتقاط الاختياري سيطابق أيضًا المسارات التي لا تضم معامل البحث (المسار <code>api/post/</code> مثالًا).  


سيكون كائن الاستعلام <code>query</code> التالي:<syntaxhighlight lang="json">
سيكون كائن الاستعلام <code>query</code> التالي:<syntaxhighlight lang="javascript">
{ } //`/api/post` سيحصل على المسار  
{ } //`/api/post` سيحصل على المسار  
{ "slug": ["a"] } //(مصفوفة وحيد العنصر) `/api/post/a` سيحصل على المسار  
{ "slug": ["a"] } //(مصفوفة وحيد العنصر) `/api/post/a` سيحصل على المسار  
سطر 82: سطر 84:
</syntaxhighlight>
</syntaxhighlight>


=== التحفظات على مسارات API الديناميكية ===
=== محاذير ===


* لوجهات API المحددة مسبقًا الأفضلية على الوجهات الدينياميكية والتي لها الأفضلية على جميع المسارات الملتقطة. إليك بعض الأمثلة:
* لوجهات API المحددة مسبقًا الأفضلية على الوجهات الدينياميكية والتي لها الأفضلية على جميع المسارات الملتقطة. إليك بعض الأمثلة:
سطر 89: سطر 91:
** <code>pages/api/post/[...slug].js</code> يتطابق مع <code>api/post/1/2/</code> و <code>/api/post/a/b/c/</code> وليس مع <code>api/post/create/</code> و <code>api/post/abc/</code>.
** <code>pages/api/post/[...slug].js</code> يتطابق مع <code>api/post/1/2/</code> و <code>/api/post/a/b/c/</code> وليس مع <code>api/post/create/</code> و <code>api/post/abc/</code>.


== استخدام أدوات API الوسطية في Next.js ==
== مساعدو الطلب Request Helpers  ==
تزوّدك مسارات API بأدوات وسطية middleware مدمجة تحلل الطلب الوارد (<code>req</code>) هي:
تزوّدك مسارات API بمساعدي الطلب request helpers يحللون الطلب الوارد (<code>req</code>) وهي:


* <code>req.cookies</code>: وهي كائن يتضمن ملفات تعريف الارتباط التي يرسلها الطلب. قيمته الافتراضية <code>{}</code>.  
* <code>req.cookies</code>: وهي كائن يتضمن ملفات تعريف الارتباط التي يرسلها الطلب. قيمته الافتراضية <code>{}</code>.  
* <code>req.query</code>: وهي كائن يتضمن نص الاستعلام، وقيمته الافتراضية <code>{}</code>.
* <code>req.query</code>: وهي كائن يتضمن الاستعلام query، وقيمته الافتراضية <code>{}</code>.
* <code>req.body</code>: وهي كائن يضم جسم الطلب يحلل وفقًا للخاصية <code>content-type</code> أو يأخذ القيمة <code>null</code> إن لم يُرسل جسم الطلب.
* <code>req.body</code>: وهي كائن يضم جسم الطلب يحلل وفقًا للخاصية <code>content-type</code> أو يأخذ القيمة <code>null</code> إن لم يُرسل جسم الطلب.


سطر 127: سطر 129:
   },
   },
}
}
</syntaxhighlight>يُفعّل الخيار تلقائيًا ويعطي تنبيهًا عندما يزيد حجم جسم الطلب عن 4 ميغابايت. فإن لم تكن تستخدم Next.js في بيئة دون خادم، وكنت تدرك جيدًا تبعات الأداء عند عدم وجود شبكة توزيع محتوى أو مضيف خاص بالوسائط، بإمكانك ألا تحدد قيمة لهذا الخيار بضبطها على <code>false</code>.<syntaxhighlight lang="javascript">
</syntaxhighlight>يُفعّل الخيار <code>responseLimit</code> تلقائيًا ويعطي تنبيهًا عندما يزيد حجم جسم الطلب عن 4 ميغابايت. فإن لم تكن تستخدم Next.js في بيئة دون خادم، وكنت تدرك جيدًا تبعات الأداء عند عدم وجود شبكة توزيع محتوى أو مضيف خاص بالوسائط، بإمكانك ألا تحدد قيمة لهذا الخيار بضبطها على <code>false</code>.<syntaxhighlight lang="javascript">
export const config = {
export const config = {
   api: {
   api: {
سطر 141: سطر 143:
</syntaxhighlight>
</syntaxhighlight>


=== دعم الأداة الوسطية Connect/Express ===
=== توسعة الكائنين <code>req</code> و <code>res</code> باستخدام TypeScript ===
بإمكانك أيضًا استخدام أداة وسطية متوافقة مع [https://github.com/senchalabs/connect Connect]. إذ يمكنك ضبط إعدادات مشاركة الموارد ذات الأصل المختلط CORS التي تستعمل مع وصلات API من خلال حزمة الأداة الوسطية [https://www.npmjs.com/package/cors cors].
 
ثبّت <code>cors</code> أولًا:<syntaxhighlight lang="bash">
npm i cors
# أو
yarn add cors
</syntaxhighlight>سنضيف تاليًا <code>cors</code> إلى مسار API:<syntaxhighlight lang="javascript">
import Cors from 'cors'
 
// cors تهيئة الأداة الوسطية
const cors = Cors({
  methods: ['GET', 'HEAD'],
})
// طريق المساعد هي انتظار الأداة الوسطية حتى تنتهي من التنفيذ قبل المتابعة
// ورمي خطأ عندما يقع أثناء تنفيذ الأداة الوسطية
function runMiddleware(req, res, fn) {
  return new Promise((resolve, reject) => {
    fn(req, res, (result) => {
      if (result instanceof Error) {
        return reject(result)
      }
 
      return resolve(result)
    })
  })
}
 
async function handler(req, res) {
  // تنفيذ الأداة الوسطية
  await runMiddleware(req, res, cors)


  // تنفيذ بقية الشيفرة
  res.json({ message: 'Hello Everyone!' })
}
export default handler
</syntaxhighlight>
=== توسعة الكائنين <code>req</code> و <code>res</code> باستخدام TypeScript في Next.js ===
نظرًا لبعض التبعات الأمنية الخاصة بالأنواع، لا يُنصح بتوسعة الكائنين <code>req</code> و <code>res</code> بل استخدم دوال للعمل معهما:<syntaxhighlight lang="javascript">
نظرًا لبعض التبعات الأمنية الخاصة بالأنواع، لا يُنصح بتوسعة الكائنين <code>req</code> و <code>res</code> بل استخدم دوال للعمل معهما:<syntaxhighlight lang="javascript">
// utils/cookies.ts
// utils/cookies.ts
سطر 241: سطر 205:
</syntaxhighlight>تذكر أن هذا الأسلوب ليس آمنًا لأن الشيفرة ستُصرَّف حتى لو لم تُصدِّر الدالة <code>()withFoo</code>.
</syntaxhighlight>تذكر أن هذا الأسلوب ليس آمنًا لأن الشيفرة ستُصرَّف حتى لو لم تُصدِّر الدالة <code>()withFoo</code>.


== مساعدو الاستجابة في Next.j ==
== مساعدو الاستجابة Response Helpers ==
يتضمن [[Node.js/http#.D8.A7.D9.84.D8.B5.D9.86.D9.81 http.ServerResponse|كائن استجابة الخادم]] Server Response object (اختصارًا <code>res</code>) مجموعة من الدوال المساعدة التي تشبه مساعدي Express.js تحسّن من تجربة المطوّر وتزيد سرعة إنشاء وصلات API جديدة. إليك بعض الدوال المساعدة:
يتضمن [[Node.js/http#.D8.A7.D9.84.D8.B5.D9.86.D9.81 http.ServerResponse|كائن استجابة الخادم]] Server Response object (اختصارًا <code>res</code>) مجموعة من الدوال المساعدة التي تشبه مساعدي Express.js تحسّن من تجربة المطوّر وتزيد سرعة إنشاء وصلات API جديدة. إليك بعض الدوال المساعدة:


* <code>res.status(code)</code>: دالة لضبط رمز الحالة التي يرد بها الخادم، وينبغي أن تكون رمز صحيح [https://academy.hsoub.com/programming/general/%D8%B1%D9%85%D9%88%D8%B2-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-http-r75/ لحالة HTTP].
* <code>res.status(code)</code>: دالة لضبط رمز الحالة التي يرد بها الخادم، وينبغي أن تكون رمز صحيح [https://academy.hsoub.com/programming/general/%D8%B1%D9%85%D9%88%D8%B2-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-http-r75/ لحالة HTTP].
* <code>res.json(body)</code>: دالة لإرسال استجابة بتنسيق JSON، وينبغي أن يكون جسم الطلب كائنًا قابلًا للسلسلة serializable object (أي يمكن تحويل صيغته إلى شكل آخر).
* <code>res.json(body)</code>: دالة لإرسال استجابة بتنسيق JSON، وينبغي أن يكون جسم الطلب كائنًا قابلًا للسلسلة serializable object (أي يمكن تحويل صيغته إلى شكل آخر).
* <code>res.send(body)</code>: دالة لإرسال استجابة HTTP التي قد تكون <code>body</code> أو <code>string</code> أو <code>Buffer</code>.
* <code>res.send(body)</code>: دالة لإرسال استجابة HTTP التي قد تكون <code>body</code> أو <code>string</code> أو <code>object</code> أو <code>Buffer</code>.
* <code>res.redirect([status,] path)</code>: دالة لتحويل الطلب إلى مسار أو عنوان URL محدد. ينبغي أن يكون المعامل <code>status</code> عبارة رمز حالة HTTP صحيح، وستكون قيمته الافتراضية "307" (إعادة توجيه مؤقت) إن لم تُحدد هذه القيمة.
* <code>res.redirect([status,] path)</code>: دالة لتحويل الطلب إلى مسار أو عنوان URL محدد. ينبغي أن يكون المعامل <code>status</code> عبارة رمز حالة HTTP صحيح، وستكون قيمته الافتراضية "307" (إعادة توجيه مؤقت) إن لم تُحدد هذه القيمة.
* <code>res.unstable_revalidate(urlPath)</code>: دالة لإعادة التحقق من الصفحة حين الطلب باستخدام الدالة <code>getStaticProps</code>. ينبغي أن يكون المعامل <code>urlPath</code> سلسلة نصية.
* <code>res.unstable_revalidate(urlPath)</code>: دالة لإعادة التحقق من الصفحة حين الطلب باستخدام الدالة <code>getStaticProps</code>. ينبغي أن يكون المعامل <code>urlPath</code> سلسلة نصية.
سطر 312: سطر 276:
   res.status(200).json({ message: 'Hello from Next.js!' })
   res.status(200).json({ message: 'Hello from Next.js!' })
}
}
</syntaxhighlight>لمعلومات أكثر، راجع توثيق استخدام [[Next.js/typescript|TypeScript في Next.js]]
</syntaxhighlight>'''ملاحظة''': نوع الكائن body في NextApiRequest هو any بسبب أن العميل يمكن أن يضمن في جسم الطلب أي شيء، لذا يجب عليك التحقق من نوعه إن كنت تتوقع شيئًا محددًا من العميل قبل التعامل معه.
 
لمعلومات أكثر، راجع توثيق استخدام [[Next.js/typescript|TypeScript في Next.js]].
 
== أمثلة ==
 
* [https://github.com/vercel/next.js/tree/canary/examples/api-routes Basic API Routes]
* [https://github.com/vercel/next.js/tree/canary/examples/api-routes-graphql API Routes with GraphQL]
* [https://github.com/vercel/next.js/tree/canary/examples/api-routes-rest API Routes with REST]
* [https://github.com/vercel/next.js/tree/canary/examples/api-routes-cors API Routes with CORS]
* [https://github.com/vercel/next.js/tree/canary/examples/api-routes-middleware API Routes Request Helpers]


== المصادر ==
== المصادر ==


* صفحات [https://nextjs.org/docs/api-routes API Routes] من توثيق Next.js الرسمي.
* صفحات [https://nextjs.org/docs/api-routes API Routes] من توثيق Next.js الرسمي.

مراجعة 19:47، 17 نوفمبر 2022

تٌعد مسارات الواجهة البرمجية API طريقة لبناء الواجهة البرمجية الخلفية لتطبيقك باستخدام Next.js الذي يعمل في هذه الحالة كواجهة أمامية frontend وواجهة خلفية backend. إذ يُربط كل ملف ضمن المجلد pages/api بالمسار */api/ ويُعامل كوصلة API (نقطة اتصال مع الواجهة البرمجية) بدلًا من كونه صفحة page. تمثّل هذه الوصلات تجمّعات bundles في جانب الخادم فقط ولن تزيد حجم تجميعة التطبيق من جانب العميل. يعيد مسار API التالي pages/api/user.js على سبيل المثال استجابة بصيغة json مع رمز الحالة 200:

export default function handler(req, res) {
  res.status(200).json({ name: 'John Doe' })
}

ملاحظة: مسارات API ستتأثر عبر ضبط pageExtensions في الملف next.config.js.

ولكي يعمل مسار API لا بدّ من تصدير دالة على أنها افتراضية default (مثل الدالة handler في المثال السابق) ومن ثم تستقبل المعاملات التالية:

  • req: يضم نسخة عن الكائن http.IncomingMessage بالإضافة إلى بعض الأدوات الوسطية middlewares المبنية مسبقًا (سنتعرف عليها لاحقًا في هذه الصفحة).
  • res: يضم نسخة عن الكائن http.ServerResponse بالإضافة إلى دوال مساعدة (سنتعرف عليها لاحقًا في هذه الصفحة).

يمكنك استخدام الكائن req.method ضمن معالج الطلب request handler إن أردت التعامل مع الأنواع المختلفة لطلبات HTTP في مسار API. إليك مثالًا:

export default function handler(req, res) {
  if (req.method === 'POST') {
    // POST يعالج طلب من النوع 
  } else {
    // يعالج بقية أنواع الطلبات
  }
}

حالات استخدام مسارت API في Next.js

يمكنك بناء الواجهة البرمجية الخلفية API بأكملها لمشروعك الجديد باستخدام مسارات API. فإن كان لديك واجهة برمجية جاهزة، لا حاجة عندها لتوجيه الاستدعاءات إليها من خلال مسارات API. إليك بعض الحالات التي يمكن استخدام مسارات API فيها:

  • إخفاء عنوان URL عن خدمة خارجية (مثال: api/secret/ بدلًا عن https://company.com/secret-url)
  • استخدام متغيرات بيئة ضمن الخادم للولوج الآمن إلى خدمات خارجية.

محاذير

مسارات API ديناميكية

تدعم مسارات API الوجهات الديناميكية dynamic routes وتخضع لجميع قواعد تسمية الملفات التي تستخدم مع الصفحات pages. فشيفرة مسار API للوجهة pages/api/post/[pid].js هي كالتالي:

export default function handler(req, res) {
  const { pid } = req.query
  res.end(`Post: ${pid}`)
}

فإن أرسل طلب إلى api/post/abc/ فسيكون الرد نصًا هو Post: abc.

الصفحة index ومسارات API الديناميكية

تهيئ المسارات بما يتطابق مع معايير REST (أو كما يُعرف RESTful) بالشكل التالي الأكثر شيوعًا:

  • GET api/posts: يُحضر قائمة بالمنشورات التي قد تكون مرتبة في صفحات.
  • GET api/posts/12345: يُحضر المنشور ذو المعرّف 12345.

يمكن نمذجة هذه الأمثلة كما يلي:

  • الخيار الأول:
    • api/posts.js/
    • api/posts/[postId].js/
  • الخيار الثاني:
    • api/posts/index.js/
    • api/posts/[postId].js/

إنّ النموذجين السابقين متكافئين. وهنالك خيار ثالث يستخدم فقط النموذج api/posts/[postId].js/ لكنه لا يصلح لأنّ المسارات الديناميكية (بما فيها التقاط جميع المسارات Catch-all routes) لا تمتلك حالة غير محددة undefined وبالتالي لن يتمكن GET api/posts من مطابقة الوجهة api/posts/[postId].js/ أبدًا.

التقاط جميع الوجهات من خلال مسار API الديناميكي

بالإمكان توسعة مسارات API الديناميكية لمطابقة كل المسارات وذلك بإضافة ثلاث نقاط ... داخل القوسين المربعين. إليك مثالًا:

  • تطابق الوجهة pages/api/post/[...slug].js المسار api/post/a/ كما تطابق api/post/a/b/ و api/post/a/b/c/ وهكذا. بإمكانك طبعًا اختيار أي اسم بدلًا من slug مثل [param...].

تُرسل معاملات البحث كمعاملات استعلام (slug مثال على ذلك) إلى الصفحة، وتشكل المعاملات دائمًا مصفوفة. وبالتالي سيكون كائن الاستعلام عن المسار api/post/a/ مثلًا هو:

{ "slug": ["a"] }

في حالة api/post/a/b/ وأية مسارات مطابقة أخرى، ستُضاف المعاملات الجديدة إلى المصفوفة كالتالي:

{ "slug": ["a", "b"] }

يبدو مسار API إلى الوجهة pages/api/post/[...slug].js كالتالي:

export default function handler(req, res) {
  const { slug } = req.query
  res.end(`Post: ${slug.join(', ')}`)
}

وهكذا سيكون النص Post: a, b, c هو الرد على الطلب api/post/a/b/c/.

التقاط جميع الوجهات اختياريًا

يمكن التقاط جميع الوجهات اختياريًا بإضافة المعامل بين قوسين مربعين مزدوجين مثل [[...slug]] إذ ستطابق الوجهة pages/api/post/[[...slug]].js مثلًا المسارات api/post/ و api/post/a/ و api/post/a/b/ وهكذا.

إنّ الاختلاف الرئيسي بين التقاط جميع الوجهات والتقاط جميع الوجهات اختياريًا هو أنّ الالتقاط الاختياري سيطابق أيضًا المسارات التي لا تضم معامل البحث (المسار api/post/ مثالًا).

سيكون كائن الاستعلام query التالي:

{ } //`/api/post` سيحصل على المسار 
{ "slug": ["a"] } //(مصفوفة وحيد العنصر) `/api/post/a` سيحصل على المسار 
{ "slug": ["a", "b"] } //(مصفوفة متعددة العناصر) `/api/post/a/b` سيحصل على

محاذير

  • لوجهات API المحددة مسبقًا الأفضلية على الوجهات الدينياميكية والتي لها الأفضلية على جميع المسارات الملتقطة. إليك بعض الأمثلة:
    • pages/api/post/create.js يتطابق مع api/post/create/.
    • pages/api/post/[pid].js يتطابق مع api/post/1/ و api/post/abc/ وليس مع api/post/create/.
    • pages/api/post/[...slug].js يتطابق مع api/post/1/2/ و /api/post/a/b/c/ وليس مع api/post/create/ و api/post/abc/.

مساعدو الطلب Request Helpers

تزوّدك مسارات API بمساعدي الطلب request helpers يحللون الطلب الوارد (req) وهي:

  • req.cookies: وهي كائن يتضمن ملفات تعريف الارتباط التي يرسلها الطلب. قيمته الافتراضية {}.
  • req.query: وهي كائن يتضمن الاستعلام query، وقيمته الافتراضية {}.
  • req.body: وهي كائن يضم جسم الطلب يحلل وفقًا للخاصية content-type أو يأخذ القيمة null إن لم يُرسل جسم الطلب.

إعدادات مخصصة

يمكن لمسار API أن يُصدّر كائن التهيئة config ليغيّر التهيئة الافتراضية وهي:

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '1mb',
    },
  },
}

يضم الكائن api كل الإعدادات المتاحة لمسار API، ويُفعَّل الخيار bodyParser (تحليل جسم الطلب) تلقائيًا. لكن بإمكانك تعطيله بضبط قيمته على false وذلك إن أردت استهلاك جسم الطلب عبر خيار النقل المستمر لبياناته Stream أو بواسطة الخيار raw-body. إحدى الحالات التي يُعطَّل فيها الخيار bodyParsing هي الحالة التي نريد فيها التحقق من القيمة الخام لجسم طلب خطاف ويب webhook مصدره GitHub مثلًا:

export const config = {
  api: {
    bodyParser: false,
  },
}

تعطي القيمة bodyParser.sizeLimit الحجم الأقصى المسموح للجسم المُحلل بأية صيغة تدعمها المكتبة bytes كالتالي:

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '500kb',
    },
  },
}

تخبر الراية externalResolver الخادم إن كان المسار سيُعالج من قبل محلل خارجي مثل express أو connect. بتفعيل هذه الراية سيوقف التنبيهات التي سببتها الطلبات غير المحللة unresolved:

export const config = {
  api: {
    externalResolver: true,
  },
}

يُفعّل الخيار responseLimit تلقائيًا ويعطي تنبيهًا عندما يزيد حجم جسم الطلب عن 4 ميغابايت. فإن لم تكن تستخدم Next.js في بيئة دون خادم، وكنت تدرك جيدًا تبعات الأداء عند عدم وجود شبكة توزيع محتوى أو مضيف خاص بالوسائط، بإمكانك ألا تحدد قيمة لهذا الخيار بضبطها على false.

export const config = {
  api: {
    responseLimit: false,
  },
}

يمكن أن تأخذ responseLimit قيمة تمثل عدد البايتات أو أية قيمة نصية تدعمها المكتبة مثل 1000 أو '500kb' أو '3mb'. تمثّل هذه القيمة أعلى حجم للاستجابة قبل عرض تنبيه تجاوز الحجم المحدد (القيمة الافتراضية هي 4 ميغابايت):

export const config = {
  api: {
    responseLimit: '8mb',
  },
}

توسعة الكائنين req و res باستخدام TypeScript

نظرًا لبعض التبعات الأمنية الخاصة بالأنواع، لا يُنصح بتوسعة الكائنين req و res بل استخدم دوال للعمل معهما:

// utils/cookies.ts

import { serialize, CookieSerializeOptions } from 'cookie'
import { NextApiResponse } from 'next'

/**
 *res ضبط ملفات تعريف الاراتباط باستخدام الكائن 
 */

export const setCookie = (
  res: NextApiResponse,
  name: string,
  value: unknown,
  options: CookieSerializeOptions = {}
) => {
  const stringValue =
    typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value)

  if ('maxAge' in options) {
    options.expires = new Date(Date.now() + options.maxAge)
    options.maxAge /= 1000
  }

  res.setHeader('Set-Cookie', serialize(name, stringValue, options))
}

// pages/api/cookies.ts

import { NextApiRequest, NextApiResponse } from 'next'
import { setCookie } from '../../utils/cookies'

const handler = (req: NextApiRequest, res: NextApiResponse) => {
    //الذي سيضيف الترويسة res استدعاء الدالة الصرفة باستخدام الكائن
    //`set-cookie` header
  setCookie(res, 'Next.js', 'api-middleware!')
    //لنتمكن من عرضها في المتصفح set-cookie إعادة الترويسة 
   
  res.end(res.getHeader('Set-Cookie'))
}

export default handler

إن لم تجد بدًا من توسعة هذين الكائنين، عليك أن تنشئ نوعًا خاصًا بك ليضم الخصائص الإضافية:

// pages/api/foo.ts

import { NextApiRequest, NextApiResponse } from 'next'
import { withFoo } from 'external-lib-foo'

type NextApiRequestWithFoo = NextApiRequest & {
  foo: (bar: string) => void
}

const handler = (req: NextApiRequestWithFoo, res: NextApiResponse) => {
  req.foo('bar') //دون أخطاء في النوع req.foo يمكن استخدام الخاصية 
  res.end('ok')
}

export default withFoo(handler)

تذكر أن هذا الأسلوب ليس آمنًا لأن الشيفرة ستُصرَّف حتى لو لم تُصدِّر الدالة ()withFoo.

مساعدو الاستجابة Response Helpers

يتضمن كائن استجابة الخادم Server Response object (اختصارًا res) مجموعة من الدوال المساعدة التي تشبه مساعدي Express.js تحسّن من تجربة المطوّر وتزيد سرعة إنشاء وصلات API جديدة. إليك بعض الدوال المساعدة:

  • res.status(code): دالة لضبط رمز الحالة التي يرد بها الخادم، وينبغي أن تكون رمز صحيح لحالة HTTP.
  • res.json(body): دالة لإرسال استجابة بتنسيق JSON، وينبغي أن يكون جسم الطلب كائنًا قابلًا للسلسلة serializable object (أي يمكن تحويل صيغته إلى شكل آخر).
  • res.send(body): دالة لإرسال استجابة HTTP التي قد تكون body أو string أو object أو Buffer.
  • res.redirect([status,] path): دالة لتحويل الطلب إلى مسار أو عنوان URL محدد. ينبغي أن يكون المعامل status عبارة رمز حالة HTTP صحيح، وستكون قيمته الافتراضية "307" (إعادة توجيه مؤقت) إن لم تُحدد هذه القيمة.
  • res.unstable_revalidate(urlPath): دالة لإعادة التحقق من الصفحة حين الطلب باستخدام الدالة getStaticProps. ينبغي أن يكون المعامل urlPath سلسلة نصية.

ضبط رمز الحالة في الاستجابة

يمكنك ضبط رمز الحالة عندما تعيد الاستجابة إلى العميل. يضبط المثال التالي قيمة رمز الحالة على "200" (كل شيء على ما يرام ok) ويعيد رسالة على شكل خاصية message قيمتها "Next.js ترحب بك!":

export default function handler(req, res) {
  res.status(200).json({ message: '!ترحب بك Next.js' })
}

إرسال استجابة بتنسيق JSON

يمكنك إعادة الاستجابة إلى العميل بتنسيق JSON، لكن ينبغي أن تكون الاستجابة كائنًا قابلًا للسلسلة. وقد ترغب في التطبيقات الحقيقية أن تخبر العميل بحالة طلبه وفقًا لاستجابة الوصلة endpoint التي طلبها.

يرسل المثال التالي الاستجابة بتنسيق JSON مع رمز الحالة "200" ونتيجة العملية غير المتزامنة. لاحظ وجود الشيفرة في كتلة try...catch لمعالجة أية أخطاء قد تحدث، وذلك بإرسال رمز الحالة ورسالة خطأ إلى العميل:

export default async function handler(req, res) {
  try {
    const result = await someAsyncOperation()
    res.status(200).json({ result })
  } catch (err) {
    res.status(500).json({ error: 'failed to load data' })
  }
}

إرسال استجابة HTTP

تُرسل وفق الآلية نفسها التي تُرسل فيها الاستجابة بتنسيق JSON، لكن الفرق الوحيد أنّ جسم الاستجابة قد يكون نصيًا string أو على شكل كائن object أو مخزن من البايتات Buffer.

يرسل المثال التالي استجابة HTTP مع رمز الحالة "200" ونتيجة العملية غير المتزامنة:

export default async function handler(req, res) {
  try {
    const result = await someAsyncOperation()
    res.status(200).send({ result })
  } catch (err) {
    res.status(500).send({ error: 'failed to fetch data' })
  }
}

تحويل الاستجابة إلى مسار أو عنوان URL محدد

قد ترغب مثلًا في تحويل العميل إلى مسار أو عنوان محدد عندما ينقر زر إرسال نموذج form. لاحظ كيف يوجّه المثال التالي العميل إلى المسار / عندما يُرسل النموذج بنجاح:

export default async function handler(req, res) {
  const { name, message } = req.body
  try {
    await handleFormInputAsync({ name, message })
    res.redirect(307, '/')
  } catch (err) {
    res.status(500).send({ error: 'failed to fetch data' })
  }
}

إضافة أنواع TypeScript

بإمكانك أن تجعل معالج الاستجابة أكثر أمانًا من ناحية النوع الذي يعيده بإدراج النوعين NextApiRequest و NextApiResponse من next كما يمكنك تحديد نوع البيانات التي ستعديدها في الاستجابة:

import type { NextApiRequest, NextApiResponse } from 'next'

type ResponseData = {
  message: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  res.status(200).json({ message: 'Hello from Next.js!' })
}

ملاحظة: نوع الكائن body في NextApiRequest هو any بسبب أن العميل يمكن أن يضمن في جسم الطلب أي شيء، لذا يجب عليك التحقق من نوعه إن كنت تتوقع شيئًا محددًا من العميل قبل التعامل معه.

لمعلومات أكثر، راجع توثيق استخدام TypeScript في Next.js.

أمثلة

المصادر

  • صفحات API Routes من توثيق Next.js الرسمي.