البحث في الموقع
المحتوى عن 'إدارة البيانات باستخدام graphql'.
-
يزداد الاعتماد على التطبيقات الذكية في جوانب حياتنا يومًا بعد يوم، ومع هذا الانتشار يزداد تعقيدها، وتتشعب وظائفها، فتصبح الحاجة ملحةً أكثر لابتكار تقنيات جديدة تحسِّن التفاعل بين خوادم التطبيقات والعملاء، ولعل أبرزها في السنوات الأخيرة GraphQL، وهي لغة استعلام مفتوحة المصدر لواجهات برمجة التطبيقات APIs وبيئة تشغيل لتنفيذ الاستعلامات، طورتها شركة فيسبوك Facebook في عام 2012 بهدف التغلب على نقاط الضعف في بنية REST التقليدية، وطرحتها للاستخدام العام في 2015، وأكثر ما يميز GraphQL جودة أدائها وكونها لغة تصريحية declarative وموجهة كليًا لتلبية طلبات العميل وما يحتاجه حقًا من معلومات. فما هي مفاهيم GraphQL الأساسية؟ وما أوجه التشابه والاختلاف بينها وبين REST؟ ما هي GraphQL؟ GraphQL هي اختصار للعبارة Graph Query Language وتعني لغة استعلام بيانية، وهي مختلفة قليلًا عن لغات الاستعلام الأخرى مثل SQL وغيرها، فهي لا تتخاطب مع قاعدة بياناتك مباشرةً إنما تصف نموذج التواصل بين العميل وخادم واجهة برمجة التطبيقات API، ولديها مجموعة مواصفات قياسية بمثابة معيار موحد يحدد خصائصها وقواعد استخدامها، وبما أنك تتبع مواصفات GraphQL، فيمكنك استخدامها مع أي لغة برمجة، ومع أي قاعدة بيانات، ومع جميع أنواع العملاء إذا كانوا قادمين من تطبيق ويب أو تطبيق هاتف محمول، فهي كما ذكرنا مفتوحة المصدر لا تقتصر على أنواع معينة. يُعد Apollo GraphQL من أشهر تطبيقات خادم وعميل GraphQL التجارية وأكثرها انتشارًا بين المطورين، وستجد في هذا المقال على أكاديمية حسوب مثالًا عمليًّا عن طريقة بنائه. خصائص GraphQL سنعرض بعضًا من خصائص GraphQL الأساسية، مثل: استعلاماتها التصريحية declarative والهرمية hierarchical، وكونها ذات قواعد صارمة في التعامل مع أنواع البيانات strongly-typed، وأيضًا استقرائية introspective تسمح بالكشف عن مواصفات مخططاتها الداخلية Schema ليستفيد منها طالب الاستعلام. تصريحية Declarative تعني التصريحية أن العميل سيحدد أو يصرح عن الحقول التي يريد الاستعلام عنها فقط ويطلبها من الخادم، والخادم بدوره سيرجعها هي بالذات دون أي معلومات إضافية. ألقِ نظرةً على المثال التالي لإيضاح الأمر. لنفترض أنك تطلب واجهة برمجية API للعبة ما وتستعلم عن حقول محددة لأحد شخصياتها، على سبيل المثال الاسم name والتصنيف race لشخصية المحارب warrior صاحب المعرف رقم "1"، فسيكون الطلب وفق الآتي: { warrior(id: "1") { name race } } ستعيد الاستجابة المُعادة من تنسيق JSON كائنًا لنسميه data يتضمن الحقلين المطلوبين فقط من بيانات المحارب رقم "1": { "data": { "warrior": { "name": "Merlin", "race": "HUMAN" } } } تتضمن هذه الاستجابة ما يطلبه العميل فقط دون زيادة أو نقصان وتمنح تطبيقك كفاءةً أعلى وأداءً أفضل على الشبكة موازنةً ببدائل GraphQL الأخرى مثل REST التي تُعيد للعميل كامل بيانات العنصر المُستَعلم عنه فتسبب ضغطًا على الشبكة. هرمية Hierarchical يمكنك طلب استعلامات هرمية من GraphQL أي الاستعلام عن أصل وفروعه، وستصلك البيانات المعادة من الخادم بنفس الهرمية التي طلبتها؛ فطلب الاستعلام يحدد شكل الاستجابة. لو عُدنا للمثال السابق واستبدلنا الاستعلام عن اسم المحارب وتصنيفه بالاستعلام عن اسمه وأسلحتهweapons وبالتحديد عن اسم كل سلاح name ودرجة قوته الهجومية attack: { warrior(id: "1") { name weapons { name attack } } } ستتضمن الاستجابة الآن اسم المحارب ومصفوفة كائنات الأسلحة weapons مرتبةً كما طلبناها في الاستعلام warrior، وقد استطاعت GraphQL إحضارها بطلب استعلام واحد فقط، رغم أن بيانات الأسلحة ومقاتلي اللعبة تكون مخزنة غالبًا في جداول منفصلة ضمن قاعدة البيانات وهذه نقطة قوتها. نذكرك هنا أن GraphQL ليست معنية مطلقًا بطريقة تخزين البيانات في قاعدة البيانات إنما بتحديد نموذج الاستعلام فقط. ألقِ نظرةً على الاستجابة الهرمية لاستعلامنا: { "data": { "warrior": { "name": "Merlin", "weapons": [ { "name": "Sword", "attack": 4 }, { "name": "Bow", "attack": 3 }, { "name": "Axe", "attack": 2 } ] } } } صارمة في تحديد الأنواع Strongly-typed توصف GraphQL بأنها صارمة في التعامل مع أنواع البيانات، ولديها نظام خاص لتحديد الأنواع يسمى نظام النوع، يصف إمكانات الخادم أي أنواع البيانات التي يقبلها، وتتدرج من البيانات المفردة Scalars وهي بيانات أولية، مثل: الأعداد الصحيحة والسلاسل النصية والقيم المنطقية، وصولًا إلى أنواع البيانات المعقدة مثل الكائنات التي تتكون من مجموعة حقول من البيانات الأولية. يبين المثال التالي إنشاء نوع في مخطط GraphQL اسمه Weapon، وهو كائن يمتلك حقولًا أولية من نوع نص String وعدد صحيح Int: type Weapon{ name: String! attack: Int range: Int } إذًا، نظام النوع هو المسؤول عن صحة تعريف مخطط GraphQL، ويُقيّم الخادم بواسطته إذا كان طلب الاستعلام الذي كتبته مقبولًا أم لا قبل تنفيذه، ثم يُخضِعه للتحقق للتأكد من سلامته قواعديًا وخلوه من الأخطاء. ذاتية التوثيق Self-documenting يدعم خادم GraphQL خاصية الاستقراء Introspection، وهذا يعطي عملاءه والبرامج المتصلة معه القدرة على استقراء مخططاته الداخلية والاستعلام عن بنيتها، ويُسهل أيضًا تطوير أدوات مساعدة للتعامل معه نحو GraphiQL التي توفر بيئة تطوير متكاملة IDE وبيئة تجريبية Playground تعمل ضمن المتصفح، وغيرها من أدوات التوثيق الآلية. هذا مثال بسيط عن استخدام الاستقراء لاستكشاف معلومات إضافية عن النوع Weapon باستعمال الكلمة المفتاحية schema__: { __schema { types { name kind description } } } سيجيبك خادم GraphQL بصيغة JSON المعتادة وفق الآتي: { "data": { "__schema": { "types": [ { "name": "Weapon", "kind": "OBJECT", "description": "A powerful weapon that a warrior can use to defeat enemies." } ] } } } موجهة بطلبات العميل Client-driven يتركز جُلّ عمل المطور عند بناء GraphQL API في الواجهة الخلفية، فيُعرّف المخطط schema وينفّذه، ويهيئ نقطة الوصول الوحيدة endpoint مع الواجهة البرمجية التي تًميّز GraphQL عن غيره وتُعدّ نقطة قوته، أما من طرف العميل فيمكنه طلب البيانات التي يريدها بدقة عبر الاستعلامات التصريحية، ومهما تغيرت مواصفات الاستعلامات، يستطيع مطور الواجهة الأمامية مواكبتها وإنجاز تصاميم تكرارية سريعة لتطبيقه دون أي تعديل إضافي على الواجهة الخلفية. بنية GraphQL يعمل GraphQL في طبقة التطبيقات Application layer وسيطًا بين العميل والبيانات، إذ يصف خادم GraphQL إمكانات الاستعلام التي تتيحها الواجهة البرمجية API، ويحدد العميل متطلبات طلب الاستعلام حسب احتياجه. الخادم Server يعمل GraphQL في طبقة التطبيق، وهو غير مرتبط ببروتوكول نقل محدد، لكنه يستخدم غالبًا بروتوكول HTTP، ولديه نقطة اتصال وحيدة تسمى عادةً graphql/ وتتيح الوصول لكل موارد الخادم. يمكنك برمجة خادم GraphQL بأي لغة برمجة، فعلى سبيل المثال تساعدك البرمجية الوسيطة express-graphql على إنشاء GraphQL API على خادم Express HTTP في بيئة Node؛ وفيما يخص قواعد البيانات فلا يقتصر خادم GraphQL على نوعٍ محدد منها، إذ يمكنه التعامل مع البيانات المخزنة في MySQL أو PostgreSQL أو MongoDB أو حتى القادمة من مصادر أخرى عبر نقاط اتصال لواجهات REST التقليدية، فالمهم في الأمر أن تُعرّف البيانات في مخطط GraphQL صحيح يبين الاستعلامات التي يستطيع العميل طلبها من الواجهة البرمجية API. العميل Client تدعى الطلبات المرسلة من عميل GraphQL إلى الخادم بالمستندات documents، وقد تكون طلبات قراءة فقط أي استعلامات، أو طلبات كتابة تسبب تعديلًا على البيانات وهذه تسمى طفرات mutations. يمكنك توجيهها بطلب XMLHttpRequest بسيط، أو بعملية fetch لجلب البيانات من متصفح الويب، أو بالاعتماد على أدوات عميل متقدمة نحو عميل Apollo أو ريلي فيسبوك التي تقدم لك مميزات مختلفة مثل التخزين المؤقت. إليك مثال لاستخدام الدالة fetch في جلب البيانات من نقطة الوصول graphql/، وقد مُرِّر مستند GraphQL بهيئة سلسلة نصية ضمن متن الطلب POST: async function fetchwarriors() { const response = await fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: `{ warriors { id name }, }`, }), }) const warriors = await response.json() return warriors } fetchwarriors() وهذه هي الاستجابة: { "data": { "warriors": [ { "id": "1", "name": "Merlin" }, { "id": "2", "name": "Gandalf" } ] } } مقارنة بين GraphQL و REST كلاهما يعملان للهدف نفسه، وهو تبادل البيانات بين تطبيقات مختلفة؛ إذ عرّفنا GraphQL في بداية المقال على أنها لغة استعلام بيانية وبيئة تشغيل لتنفيذ الاستعلامات؛ أما REST فهي اختصارٌ للعبارة Representational State Transfer وهي معمارية شهيرة لمشاركة البيانات عبر الويب، و RESTful API هي واجهة برمجة تطبيقات تتبع معايير REST، مثل: انعدام الحالة stateless، وقابلية التخزين في ذاكرة التخزين المخبئية Cache، واستقلال التقنيات المستخدمة في جانبي العميل والخادم عن بعضها، إضافةً إلى الواجهة المعيارية الموحدة التي تستخدم معرفات فريدة مثل عناوين URIs وغيرها. تُعد GraphQL أحدث من REST وقد بُنيت في الأساس لمعالجة نقاط ضعفها بإنشاء واجهة API عالية الكفاءة وموجهة للعميل. لنبدأ الآن الموازنة بينهما لنعرف مزايا وعيوب كل تقنية: البنية: تحتوي بنية REST نقاط وصول متعددة للتخاطب مع الخادم، أما GraphQL فتستخدم نقطة وصول وحيدة، وستعطيك مخططًا للبيانات التي طلبتها باستعلام واحد مهما كانت معقدة ومتشعبة، أما في REST فستحتاج عددًا من الاستعلامات للحصول على البيانات المتشعبة نفسها. إذًا، تتفوق GraphQL على REST في تخفيف الضغط على الشبكة. جلب البيانات: عندما تستعلم عن تفصيل ما من واجهة REST تجيبك بأكثر مما طلبت، وترسل لك مجموعة البيانات المرتبطة بهذا التفصيل كاملةً كما هي معرفة على الخادم، حتى لو كنت تحتاج رقمًا واحدًا منها فقط، وفي حالاتٍ أخرى لا تكون استجابتها كافية، فعلى سبيل المثال قد لا تعطيك نقطة الوصول الخاصة بقائمة ما على الخادم جميع الخصائص التي تريد معرفتها عن القائمة فستحتاج نقاط أخرى معها أيضًا، بينما تخلصك GraphQL باستعلاماتها التصريحية من هذا الإفراط أو التقصير في جلب البيانات، وتحضرها لك بالهيكلية التي يحددها العميل دون زيادة أو نقصان. التعامل مع الأخطاء: لا ينحصر استخدام GraphQL مع بروتوكول HTTP فقط، بل يمكنها الاعتماد على غيره أيضًا، لذا فهي لا تعتمد على رموز استجابة HTTP لعرض أخطاء طلباتها للعميل، فمعظم الطلبات العائدة من نقاط وصول GraphQL ستحمل الرمز 200 سواء كانت صحيحة أو خاطئة، وستجد ضمن استجابة الطلبات الخاطئة رسائل واضحة عن أخطائك جنبًا إلى جنب مع البيانات data وهذا بفضل خاصية الأخطاء errors، أما واجهة RESTful API فتعتمد كليًا على رموز استجابة HTTP، فيشير الرمز 200 دومًا للطلبات الصحيحة، وتعبّر رموز 400 عن الطلبات الخاطئة دون أي تفاصيل عن طبيعة الخطأ. الإصدارات: تسعى GraphQL في جميع تعديلاتها لتجنب التغيرات الجذرية breaking changes، التي من المحتمل أن تسبب أخطاء في جانب العميل، ويحاول مطوروها الحفاظ على التوافقية مع الإصدارات السابقة، فهي توفر إمكانية زيادة ميزات جديدة على الواجهة بإضافة أنواع بيانات جديدة وحقول جديدة بدون الحاجة لإنشاء إصدار جديد، على عكس REST التي تتعامل مع أي تعديل على أنه إصدار جديد لنقاط الوصول، ويُشار للإصدارات صراحةً في عناوين URL، فتجد فيها رموزًا مثل: V1/ أو V2/، ولهذه الآلية أيضًا مشكلاتها. يمكنك عمومًا التعامل مع تغيُّر الإصدارات في التقنيتين وإن كانت طريقة REST أكثر تقليدية. التخبئة Caching: تُعد التخبئة مبدأ من مبادئ REST، ولكونها تعتمد على توابع HTTP لجلب البيانات من نقاط وصولها المتعددة، فبوسعها الاستفادة من ميزة التخبئة -إحدى أساسيات HTTP- وعدم إعادة جلب الموارد نفسها من الخادم في كل مرة يحتاجها العميل. بالمقابل لا تستفيد GraphQL من ميزة التخبئة المُضمّنة في HTTP، لأنها تستخدم نقطة وصول وحيدة لجميع الطلبات، وكل طلب من طلباتها مخصص وفريد عن غيره، ومع ذلك يستطيع عملاء GraphQL استخدام التخبئة بصورة مبسطة باستعمال المُعرّف العمومي للكائن. لنختتم الفقرة الآن بجدول يلخص أوجه الشبه والاختلاف بين GraphQL و REST، واضعًا في حسبانك إمكانية استخدامهما معًا في مشروع واحد، فتكون GraphQL مثل بوابة أو نقطة تجميع تتلقى البيانات الواردة من خدمات REST. الميزة GraphQL REST الوصف GraphQL هي لغة استعلام لواجهات برمجة التطبيقات، ووقت تشغيل من جانب الخادم لتنفيذ الاستعلامات REST هي نمط معماري لتصميم خدمات الويب جلب البيانات عبر نقطة وصول وحيدة تستقبل طلبات الاستعلام المحددة بدقة من طرف العميل والمنقولة ببروتوكول HTTP عبر عدة نقاط وصول HTTP تعيد للعميل مجموعة بيانات محددة مسبقًا من قبل الخادم الإصدارات غير شائعة شائعة رموز استجابة HTTP كافة الاستجابات حتى الخاطئة منها تعود بالرمز 200 تطبق رموز استجابة HTTP بدلالتها المعروفة التحقق التحقق من البيانات الوصفية مضمن في GraphQL المطور يجري عمليات التحقق بنفسه يدويًا التوثيق التوثيق ذاتي بفضل وجود نظام النوع وميزة الاستقراء لا يوجد توثيق ذاتي لكن يمكنك الاستفادة من أدوات خاصة مثل OpenAPI التخبئة غير متاح متاح أساليب الطلب جميع الطلبات سواء كانت استعلامات أو طفرات أو اشتراكات تُرسل بأسلوب POST عبر بروتوكول HTTP تستخدم كل أساليب HTTP مثل GET و POST و PUT و PATCH و DELETE وغيرها صيغة الاستجابة JSON بأي صيغة مثل JSON و XML و HTML وغير ذلك الخاتمة كان هذا المقال الأول من سلسلة مقالات تتناول إدارة البيانات باستخدام GraphQL، وقد تعرفنا فيه على GraphQL لغة الاستعلام مفتوحة المصدر الخاصة بواجهات برمجة التطبيقات، هذه اللغة التي طورتها فيسبوك موجهةً إياها كليًا لتلبية طلبات العميل، عبر كتابة استعلامات تصريحية يوضح فيها ما يحتاجه فعلًا، لتتجاوز بذلك مشكلات REST التقليدية، مثل الإفراط أو التقصير في جلب البيانات، إضافةً إلى انخفاض الكفاءة على الشبكة. لا يعتبر المقال GraphQL بديًلا لواجهات REST، فلكلٍ منهما تطبيقات خاصة تتميز فيها عن الأخرى، وأيضًا طريقة خاصة في إدارة البيانات المتبادلة بين العميل والخادم، وقد عرضنا هنا طريقة GraphQL في عرض البيانات، تابع معنا بقية مقالات السلسلة لتطلع على المزيد. ترجمة -وبتصرف- للمقال An Introduction to GraphQL لصاحبته Tania Rascia. اقرأ المزيد مدخل إلى المكتبة GraphQL واستعمالاتها في بناء تطبيقات الويب الحديثة. شرح فلسفة RESTful - تعلم كيف تبني واجهات REST البرمجية. دليلك الشامل إلى برمجة التطبيقات.
-
تُسهّل GraphQL الاتصال بين الواجهة الأمامية لتطبيقك وقاعدة البيانات عبر استعلاماتها التصريحية والموجهة بطلبات العميل، والبنية التي توضح إمكانات هذه الاستعلامات وتفاصيلها هي مخططات GraphQL المحكومة بنظام النوع، فما هو نظام النوع؟ وكيف يساعدك فهمه على بناء مخططات فعالة تحقق لك الفائدة المرجوة من GraphQL؟ سنعرض ذلك مع بعض الأمثلة التطبيقية على كل نوع بدءًا من الأنواع الخمسة المفردة المبنية مسبقًا built-in scalar، والتعدادات Enums، والقوائم والواجهات Interfaces والأنواع غير الفارغة الغالفة non-null wrapping، ونوع الكائن Object، وأنواع التجريد Abstract والاتحاد Union أيضًا. متطلبات العمل ستحتاج هذه الأساسيات لتطبيق الأمثلة الموجودة هنا: الاطلاع على المقال الأول مقدمة إلى GraphQL لفهم المبادئ الأساسية للتقنية. بيئة عمل تحوي خادم GraphQL، يمكنك تجهيزها بتنفيذ الخطوات الموجودة في مقالنا الثاني إعداد خادم GraphQL في بيئة Node.js. الأنواع المفردة Scalar إذا شبهت استجابة GraphQL بالشجرة متفرعة الأغصان فستكون البيانات المفردة هي الأوراق في نهاية كل غصن، ففي نهاية الأمر تتحلل جميع البيانات في مخطط GraphQL مهما بلغ تعقيدها وتداخلها إلى بيانات مفردة، وللبيانات المفردة خمسة أنواع: نوع العدد الصحيح Int Int هو نوع عدد صحيح لا يحتوي فاصلة عشرية، ومُؤشّر بإشارة موجب أو سالب، ويتكون من 32 بت، لذا تتراوح قيمته بين "2,147,483,647-" و "2,147,483,647"، والعدد الصحيح هو واحد من نوعين فقط لتمثيل الأعداد في GraphQL. نوع العدد العشري Float Float هو نوع عدد عشري ثنائي الدقة، مثل 1.2، ومُؤشّر بإشارة موجب أو سالب، وهو النوع المفرد الثاني لتمثيل الأعداد في GraphQL. نوع السلسلة النصية String String هو نوع سلسلة محارف بترميز UTF-8 تُستخدم لكتابة النصوص والأعداد الكبيرة جدًا، وتُعد السلاسل النصية أكثر الأنواع المفردة استخدامًا. النوع البولياني Boolean يحمل النوع البولياني Boolean إما قيمة صحيحة true أو خاطئة false. نوع الرقم التعريفي ID ID هو رقم تعريفي فريد، يُمثّل دائمًا بصيغة سلسلة نصية حتى لو تضمن أرقامًا، ويُولّد غالبًا بواسطة خوارزميات المُعرّف العالمي الفريد Universally Unique Identifier أو اختصارًا UUID. أنواع مفردة مخصصة تغطي الأنواع المفردة الخمسة السابقة معظم حالات الاستخدام، لكنك قد تحتاج أنواعًا أخرى تناسب طبيعة تطبيقك، مثل الوقت Time، والتاريخ Date، وعناوين url، والتي يمكنك تعريفها باستعمال الكلمة المفتاحية scalar، وسيعتمد الخادم عندها هذه الأنواع معيارًا للتحقق من صحة بياناتك المدخلة ومطابقتها لمواصفات النوع المخصص الذي تنتمي إليه. إليك مثالًا عن تعريف نوع التاريخ Date: scalar Date يستخدم خادم GraphQL الوحدة البرمجية GraphQLScalarType للتعامل مع الأنواع الجديدة المُضافة. نوع التعداد Enum يصف التعداد مجموعةً من القيم المحتملة للعنصر. لو طبقت الأمر على اللعبة التي استخدمناها مثالًا في مقالات السلسلة، ستجد أنك تحتاج نوع التعداد عند تعريف وظائف شخصيات اللعبة Job، وأنواعهم Species، وأسلحتهم وما إلى ذلك؛ فلكل شخصية قيم محددة لا ينبغي أن يقبل النظام غيرها. يُعرّف نوع التعداد باستعمال الكلمة المفتاحية enum، وتكتب التعدادات ضمنه بالحروف الإنجليزية الكبيرة كما يلي: "The job class of the character." enum Job { FIGHTER WIZARD } "The species or ancestry of the character." enum Species { HUMAN ELF DWARF } تضمن بهذه الطريقة أن وظيفة الشخصية Job ستكون إما مقاتل FIGHTER أو ساحر WIZARD فقط، ولن يقبل الخادم أي كلمة أخرى مهما كانت؛ أما لو عرّفت وظيفة الشخصية على أنها سلسلة نصية عادية String، فستكون جميع الكلمات سيان عند الخادم وسيقبل أي مجموعة محارف تُعطى له. تفيدك التعدادات أيضًا في تمرير القيم الوسيطة، إذ يمكنك مثلًا تعريف تعداد enum لتحديد إذا كان سلاح الشخصية يُحمَل بيد واحدة مثل السيف أم أنه ثقيل مثل الفأس ويحتاج لكلتا اليدين، سيساعدك هذا التعداد على التحكم بتجهيز الشخصية بسلاح واحد أو بسلاحين، لنسمي التعداد اليد Hand، ونعرّفه بالتعليمات التالية: enum Hand { SINGLE DOUBLE } "A valiant weapon wielded by a fighter." type Weapon { name: String! attack: Int range: Int hand: Hand } type Query { weapons(hand: Hand = SINGLE): [Weapon] } صُرّح عن التعداد Hand بحالتين لاستعمال اليد، مفردة SINGLE، ومزدوجة DOUBLE، أما الوسيط المسند إلى الحقل weapons في الاستعلام فيحمل القيمة الافتراضية SINGLE، وهي القيمة التي يأخذها في حال لم تمنحه قيمة مغايرة. النوع غير الفارغ Non-Null قد تستغرب عدم وجود النوع null أو undefined بين أنواع GraphQL المفردة رغم أنه يُعدّ نوعًا شائعًا في معظم لغات البرمجة، ويرجع السبب في ذلك إلى أن جميع أنواع GraphQL تقبل القيمة Null افتراضيًا وفي حال أردت تغيير الأمر عليك استخدام إشارة التعجب مع اسم النوع. يُعرّف النوع غير الفارغ Non-Null في GraphQL بأنه مُعدِّل للأنواع الأخرى، فمثلًا الحقل String هو حقل اختياري قد يحمل قيمة أو لا، أما الحقل من النوع !String فهو إجباري أي لا يجب أن يكون فارغًا. نوع القائمة List نوع القائمة هو أيضًا من الأنواع المُعدِّلة للأنواع الأخرى، فأي نوع يوضع بين قوسين مربعين [] يتحول لقائمة. على سبيل المثال يُعرّف النوع [Int] قائمةً من نوع الأعداد الصحيحة Int والنوع [String] قائمةً من السلاسل النصية String وقِس على ذلك، ويمكنك استخدام القائمة مع إشارة التعجب لتحصل على نوع غير فارغ ومُعرّف على أنه قائمة مثل ![String]. نوع الكائن Object شبهنا الأنواع المفردة بأوراق الشجرة في بداية المقال، وتبعًا لهذا التشبيه تكون الكائنات هي الأغصان، وهي معظم ما سنكتبه في مخطط GraphQL. يبدأ تعريف الكائن بالكلمة المفتاحية type، ويحتوي كل كائن على حقل واحد على الأقل. تُسمى الحقول بالمفاتيح وبجانب كل حقل تجد نوع القيمة التي يقبلها. لا يجوز أن تبدأ أسماء الحقول برمز الشرطتين السفليتين __، فهو مخصص لنظام الاستقراء. لو أعطينا مثالًا في سياق أمثلتنا السابقة عن اللعبة، فيمكنك إنشاء كائن يمثل المقاتلين Fighter: "A hero with direct combat ability and strength." type Fighter { id: ID! name: String! level: Int active: Boolean! } يمتلك هذا الكائن أربعة حقول: id رقم تعريفي من نوع ID غير فارغ. name سلسلة نصية String غير فارغة. level عدد صحيح Int. active قيمة منطقية Boolean غير فارغة. ونود الإشارة هنا إلى قدرتك على كتابة تعليق قبل تعريف الكائن مباشرةً بين علامتي اقتباس، مثل: "A hero with direct combat ability and strength." وسيظهر تعليقك بمثابة الوصف للكائن. قد تكون حقول الكائن بيانات مفردة كما في مثالنا السابق أو كائنات أخرى. ألقِ نظرةً على المثال أدناه، إذ عرّفنا في البداية كائنًا من نوع السلاح Weapon يتضمن أسماء الأسلحة وطبيعة هجومها ومدى تأثيرها، ثم أضفنا السلاح weapon على أنه أحد حقول كائن المقاتل Fighter وهو weapon. يمكن لمخطط GraphQL أن يُضبط بحيث يعالج الحقل weapon الكائن Weapon: "A valiant weapon wielded by a fighter." type Weapon { name: String! attack: Int range: Int } "A hero with direct combat ability and strength." type Fighter { id: ID! name: String! level: Int active: Boolean! weapon: Weapon } يمكن أن تتداخل الكائنات ببعضها ويشترك أكثر من كائن بالحقل نفسه. أنواع عمليات الجذر عمليات الجذر هي أنواع خاصة من الكائنات تنطبق عليها جميع قواعد الكائنات، تمثل جذور مخطط GraphQL وتسمى نقاط دخول entrypoints، ولها ثلاثة أنواع هي الاستعلامات والطفرات والاشتراكات. تُعرّف هذه العمليات ضمن كائن الجذر schema في مخطط GraphQL، والمميز بالكلمة المفتاحية schema. ألقِ نظرةً على التعليمات التالية: schema { query: Query mutation: Mutation subscription: Subscription } لنبدأ بنوع الاستعلام Query type، وهو عملية أساسية لا غنى عنها في أي مخطط GraphQL، ويمثّل عملية قراءة، ويقابل الطلب GET في واجهة REST API. ألقِ نظرةً على تعريف استعلام الجذر Query للعبة نفسها، إذ يُرجِع هذا الاستعلام قائمة بالمقاتلين: type Query { fighters: [Fighter] } أما الطفرات فتمثل طلبات الكتابة لأنها تعدل على المخطط، فهي تشبه طلبات PUT و DELETE و POST في REST API. يبين المثال أدناه الطفرة Mutation التي تستخدم الوسيط input لإضافة مقاتل addFighter: type Mutation { addFighter(input: FighterInput): Fighter } عملية الجذر الأخيرة هي الاشتراكات Subscription، التي تفيدك في حالات تدفق الأحداث بين الخادم والعميل، وتعمل بتوافق تام مع تقنية WebSocket. بالعودة للعبة، يمكنك استخدام الاشتراكات لتمثيل الاصطدامات العشوائية في المعارك مثلًا على النحو التالي: type Subscription { randomBattle(enemy: Enemy): BattleResult } وسطاء الحقول Field Arguments حقول الكائن في GraphQL هي دوال تقليدية ترجِِع قيمًا وتقبل متغيراتٍ وسيطة مثلها مثل الدوال الأخرى، وتنتمي وسطاء الحقل إلى أي نوع غير فارغ من أنواع GraphQL باستثناء الكائنات، وتُعرّف بكتابة اسم المتغير يليه نوعه. ألقِ نظرةً على المثال التالي لترشيح الكائن Fighter باستخدام الحقل id، ولاحظ إشارة التعجب بعد النوع للدلالة على عدم قبول قيم ID الفارغة. type Query { fighter(id: ID!): Fighter } تلعب الوسطاء في هذا المثال دورًا في ترشيح كائن المقاتل Fighter بحسب الرقم التعريفي، فتمكّنك من جلب عنصر محدد من مخزن البيانات، وتستطيع استخدامها لأي استعلامات أخرى تريدها. نوع الواجهة Interface تشبه بنية نوع الواجهة بنية الكائنات، فهي تتكون من عدة حقول لكل منها اسم ونوع، والغاية منها وضع نموذج مشترك يمكن تطبيقه على مجموعة كائنات تمتلك حقولًا مشتركة. أنشأنا في الفقرات السابقة كائن المقاتل Fighter وهو يشترك في بعض حقوله مع كائنات أخرى تلعب أدوارًا في اللعبة، مثل المعالجين Healer والسحرة Wizard وغيرهم. يمكنك في هذه الحالة إنشاء واجهة تتضمن الحقول المشتركة بين جميع هذه الشخصيات، ثم إنشاء كائنات منفصلة لكل شخصية تكون تطبيقًا للواجهة. ألقِ نظرةً على المثال التالي لتوضيح الأمر. أنشئ في البداية واجهة تسمى BaseCharacter تبدأ بالكلمة المفتاحية interface وتتضمن كل الحقول المشتركة بين الشخصيات، كما يلي: "A hero on a quest." interface BaseCharacter { id: ID! name: String! level: Int! species: Species job: Job } لكل شخصية في اللعبة رقم تعريفي خاص id واسم name ومستوى level ونوع species وعمل job. خذ مثلًا نوع المقاتل Fighter ونوع الساحر Wizard لدى كل منهم جميع الحقول الموجودة في الواجهة BaseCharacter مع بعض الاختلاف؛ فالمقاتل يستعمل الأسلحة Weapon؛ والساحر يستعمل التعويذات Spell. يمكنك إذًا تطبيق الواجهة لتسهيل تعريف النوعين، وذلك باستخدام الكلمة المفتاحية implements، وفق التالي: "A hero with direct combat ability and strength." type Fighter implements BaseCharacter { id: ID! name: String! level: Int! species: Species job: Job! weapon: Weapon } "A hero with a variety of magical powers." type Wizard implements BaseCharacter { id: ID! name: String! level: Int! species: Species job: Job! spells: [Spell] } تذكر أن شرط تطبيق الواجهة على كائن معين هو امتلاك الكائن لجميع حقول الواجهة دون استثناء. نوع الاتحاد Union يمثل نوع الاتحاد مجموعة كائنات تصلح لأن تكون استجابة لاستعلام واحد. لننشئ مثلًا اتحادًا لشخصيات اللعبة Character تتضمن كافة كائنات الشخصيات، وهي في حالتنا كائنات المقاتل Fighter والساحر Wizard. union Character = Wizard | Fighter يبدأ تعريف الاتحاد بالكلمة المفتاحية Union، والتي تكون مدخلاتها إما كائنات أو واجهات فهي لا تقبل الأنواع المفردة. ترمز إشارة اليساوي = ضمن التعريف إلى إسناد القيمة، أما الخط الرأسي | فيكافئ عبارة OR المنطقية. الآن، إذا رغبت بالاستعلام عن شخصيات اللعبة فيمكنك استخدام الاتحاد Character ليستجيب الخادم بقائمة تضم جميع الكائنات من نوع مقاتل Fighter أو ساحر Wizard. الخاتمة تعرّفنا في هذا المقال على أنواع GraphQL المختلفة، شبهنا المخطط بالشجرة فكانت الأنواع المفردة بمثابة الأوراق التي ينتهي إليها كل شيء، فهي أنواعٌ تساهم بتشكيل الأنواع الأخرى، ولها خمسة أنواع أساسية هي Int و Float و String و Boolean و ID ويمكنك إنشاء أنواعك الخاصة، ثم عرضنا نوع التعداد الذي يمنحك تحكمًا أكبر في استجابة الخادم فتزوده بمجموعة محددة من القيم الصالحة، وبعدها اطلعنا على مُعدِّلات الأنواع أي القائمة والقيم غير الفارغة التي تُغيّر طبيعة الأنواع الأخرى، وتوسعنا في الكائنات لأنها عماد مخططات GraphQL فهي أشبه بأغصان الشجرة بما فيها من كائنات جوهرية تتمثل في عمليات الجذر أي الاستعلامات والطفرات والاشتراكات، ولا ننسَ الواجهات والروابط التي تُسهّل علينا التعامل مع الكائنات ذات الخصائص المشتركة. لديك الآن أساسٌ نظري جيد ننصحك ببناء خادمك الخاص بمساعدة مقال إعداد خادم GraphQL في بيئة Node.js والتدرب عمليًّا على تطبيق الأنواع لحين إتقانها. ترجمة -وبتصرف- للمقال Understanding the GraphQL Type System لصاحبته Tania Rascia. اقرأ أيضًا المقال السابق إعداد خادم GraphQL في بيئة Node.js دليلك الشامل إلى أنواع البيانات مقدمة إلى Node.js كتابة أول برنامج في بيئة Node.js وتنفيذه مدخل إلى المكتبة GraphQL واستعمالاتها في بناء تطبيقات الويب الحديثة النسخة الكاملة لكتاب البرمجة باستخدام Node.js
-
GraphQL لغة استعلام مفتوحة المصدر لواجهات برمجة التطبيقات API وبيئة تشغيل لتنفيذ الاستعلامات، وأُنشِئَت في الأساس لمعالجة مشكلات REST التقليدية وقد عرضنا بعضًا منها، وسنطّلع في هذا المقال على آلية تفاعل مكونات GraphQL مع بعضها، عبر بناء خادم GraphQL API في بيئة Node.js، وسيُستخدم خادم Express API بدلًا من Apollo GraphQL رغم أنه الأشهر بين تطبيقات GraphQL. ستتعلم خلال متابعتك لخطوات العمل إنشاء مخطط GraphQL متوافق مع نظام الأنواع بما فيه من عمليات، مثل الاستعلامات والطفرات ومحللات الاستجابة، وستتآلف مع بيئة التطوير المتكاملة GraphiQL، إذ سنستعملها لاستعراض المخطط وتصحيح أخطائه، ولطلب استعلامات العميل من واجهة GraphQL. متطلبات العمل جهّز المتطلبات التالية لتتابع معنا سير العمل: بيئة Node.js جاهزة على خادمك المحلي، ويمكنك الاسترشاد بأحد المقالين تثبيت Node.js على نظام أبونتو 18.04 أو كيفية تثبيت Node.js على Debian 8 لإعدادها. فهم أساسيات GraphQL. الإلمام ببروتوكول HTTP، ننصحك بقراءة مدخل إلى HTTP. معرفة أساسية بكل من HTML و JavaScript، تفيدك هذه المقالات أساسيات إنشاء موقع ويب باستخدام تعليمات HTML، وأساسيات لغة جافاسكربت. إعداد خادم Express HTTP يبدأ إعداد خادم Express بتثبيت الحزمتين express و cors، باستخدام الأمر npm install ضمن مشروع جديد وفق التالي: npm install express cors Express هو إطار لتطبيقات الويب المبنية على Node.js، وهو مصمم خصيصًا لبناء واجهات برمجة التطبيقات، وسيكون إطار العمل الخاص بخادمنا، أما الحزمة CORS وهي اختصار لمصطلح البرمجيات الوسيطة لمشاركة الموارد ذات الأصل المختلط Cross-Origin Resource Sharing، إذ إن الغاية منها هي السماح لمتصفحك بالوصول إلى موارد الخادم. ثبّت أيضًا الاعتمادية Nodemon مع الراية D بصفتها اعتمادية تطوير، لتستخدمها في مرحلة التطوير فقط: npm install -D nodemon Nodemon هي أداة مساعدة في تطوير تطبيقات Node، تعيد تشغيل التطبيق في كل مرة يطرأ فيها تغيير على ملفات المشروع الموجودة في مجلد الجذر لتأخذ التعديلات مفعولها. سينشأ بتثبيت هذه الحزم المجلد node_modules والملف package.json مع اعتماديتي إنتاج واعتمادية تطوير. إذا فتحت الملف package.json بمحرر نصوص مثل نانو nano ستجده يحتوي المعلومات التالية: { "dependencies": { "cors": "^2.8.5", "express": "^4.17.3" }, "devDependencies": { "nodemon": "^2.0.15" } } عدّل عليه ليصبح وفق التالي: { "main": "server.js", "scripts": { "dev": "nodemon server.js" }, "dependencies": { "cors": "^2.8.5", "express": "^4.17.3" }, "devDependencies": { "nodemon": "^2.0.15" }, "type": "module" } لنشرح التعديلات السابقة، إذ وضعنا ملف الخادم "server.js" -الذي سننشئه بعد قليل- في الحقل main، وبهذا تتأكد من تشغيل npm start للخادم؛ أما الملف النصي المسمى "dev"، فيُسهل التطوير إذ يتضمن أمر تشغيل nodemon server.js. أضفنا في السطر الأخير القيمة module لحقل النوع type لنُفعّل إمكانية استخدام تعليمة الاستيراد import عوضًا عن require -تعليمة CommonJS الافتراضية- وفي هذا أيضًا تسهيلٌ للعمل. احفظ التغييرات، وأغلق الملف، لننتقل لإنشاء ملف الخادم. سنبني الآن خادم Express بسيط عبر إنشاء ملف نصي باسم "server.js"، يتضمن التعليمات المبينة أدناه، يعمل هذا الخادم على المنفذ 4000، وتظهر في صفحته الأولى عبارة الترحيب !Hello, GraphQL: import express from 'express' import cors from 'cors' const app = express() const port = 4000 app.use(cors()) app.use(express.json()) app.use(express.urlencoded({ extended: true })) app.get('/', (request, response) => { response.send('Hello, GraphQL!') }) app.listen(port, () => { console.log(`Running a server at http://localhost:${port}`) }) لاحظ أن الملف يبدأ بتعليمة استيراد الدالة express، ثم بعض الإعدادات الخاصة بكل من CORS و JSON، وبعدها استعملنا ('/')app.get لتحديد ما ينبغي إرساله إلى الجذر / ضمن طلبات GET، وفي نهاية الملف استخدمنا ()app.listen لتحديد منفذ خادم API. احفظ الآن التغيرات، وأغلق الملف، ثم نفذ الأمر التالي لتشغيل خادم Node: npm run dev تأكد -قبل الانتقال للخطوات التالية- من سلامة تشغيل الخادم بظهور صفحة الترحيب !Hello, GraphQL عند فتح الرابط "http://localhost:4000" من المتصفح أو تشغيل الأمر: curl http://localhost:4000 إعداد البرمجية الوسيطة لخادم GraphQL HTTP يوجد ثلاث خطوات أساسية لتحقيق التكامل بين مخطط GraphQL مع خادم إكسبريس، وتتلخص في إعداد الاتصال مع مخزن البيانات، وتعريف المخطط، وتعريف محللات الاستجابة. ستحتاج في البداية لتنفيذ الأمر التالي: npm install graphql@14 express-graphql @graphql-tools/schema الذي يثبت الحزم الضرورية للعمل مع GraphQL، وهي: graphql: الحزمة المرجعية التي تستخدمها لغة جافا سكربت لجعل التعامل مع GraphQL متاحًا في اللغة. express-graphql: البرمجية الوسيطة لخادم HTTP للتعامل مع GraphQL. graphql-tools/schema@: مجموعة أدوات تُسرّع تطوير GraphQL. ضِف تعليمات استيراد هذه الحزم إلى الملف "server.js"، لتصبح بدايته على النحو التالي: import express from 'express' import cors from 'cors' import { graphqlHTTP } from 'express-graphql' import { makeExecutableSchema } from '@graphql-tools/schema' ... لننجز الآن إعدادات الاتصال مع مخزن البيانات قبل إنشاء مخطط GraphQL القابل للتنفيذ. أنشئ الكائن data بإضافة التعليمات الخاصة به إلى الملف "server.js" مباشرةً بعد تعليمات الاستيراد كما هو مبين أدناه، إذ يمثل هذا الكائن مخزنًا للبيانات في الذاكرة in-memory store، ويحتوي مجموعة بيانات بسيطة لنستعلم عنها من خادم GraphQL. نخفف بهذه الطريقة عبء إنشاء قاعدة بيانات حقيقية والاتصال معها كوننا نجهز بيئة تعليمة بسيطة. import express from 'express' import cors from 'cors' import { graphqlHTTP } from 'express-graphql' import { makeExecutableSchema } from '@graphql-tools/schema' const data = { warriors: [ { id: '001', name: 'Jaime' }, { id: '002', name: 'Jorah' }, ], } … تمثّل هيكلة البيانات جدولًا بسيطًا يدعى warriors، الذي يحتوي سطرين 'Jaime' و 'Jorah' لبيانات اثنين من محاربي لعبة افتراضية سنستخدمها في مثالنا. ملاحظة: لا يغطي هذا المقال حالة اتصال خادم GraphQL بقاعدة بيانات حقيقية، وعادةً ما يستخدم خادم GraphQL دوال الاختزال reducers للوصول إلى قواعد البيانات والتعديل عليها، ويعتمد تقنياتٍ مثل Prisma، وهي أداة ربط العلاقات بالكائنات ORM، أما إعدادات الاتصال مع قاعدة البيانات فيحملها المعامل context أحد معاملات محللات الاستجابة غير المتزامنة. سيمثل المتغير data إعدادات اتصالنا مع مخزن البيانات حتى نهاية هذا المقال. الآن، بعد تثبيت الحزم المطلوبة، وتجهيز بعض البيانات للاستعلام، يمكننا إنشاء المخطط الذي يُعرّف واجهة API، ويصف ماهية البيانات الممكن الاستعلام عنها. مخطط GraphQL سننشئ مخططًا أوليًّا لواجهة API يتضمن الحد الأدنى من التعليمات البرمجية التي تسمح لنا بالاتصال مع نقطة الوصول GraphQL Endpoint. سنفترض في هذا المثال أننا نستعلم عن بيانات لعبة مغامرات فيها أدوار لشخصيات مختلفة، مثل المحاربين والسحرة والمعالجين وغيرهم، لذا سنحرص عند إنشاء المخطط أن يكون مرنًا وقابلًا لإعادة الاستخدام، لتستطيع تكراره مع أي نوع من شخصيات اللعبة وتوسعته ليشمل متغيرات أخرى تخصهم مثل الأسلحة والتعويذات. تستند مخططات GraphQL إلى نظام الأنواع، ويمكنك دائمًا إنشاء أنواعك الخاصة إذا لم تلبِ الأنواع الافتراضية المُضمّنة في النظام حاجتك، وهذا ما سنفعله هنا إذ سننشئ نوعًا type يدعى المحارب warrior له حقلين id و name. type Warrior { id: ID! name: String! } الحقل id نوعه ID رقم تعريفي، والحقل name نوعه سلسلة نصية String، وكلاهما من الأنواع الأولية المُضمّنة في نظام الأنواع، والأنواع الأولية تستخدم لإنشاء أنواع أعقد مثل الكائنات، أما إشارة التعجب ! الواردة في التعليمات أعلاه فتشير لكون الحقل بجانبها لا يقبل قيمة فارغة "Null"، فلا يجب أن يكون فارغًا بأية حال. الآن بعد إنشاء نوع المحارب، سننشئ نوع الاستعلام Query، وهو نقطة الدخول entry point إلى استعلام GraphQL. ألقِ نظرةً على التعليمات المبينة أدناه، إذ يُعرّف هذا النوع المحاربين warriors على أنهم مصفوفة من البيانات التابعة لنوع المحارب Warrior. type Query { warriors: [Warrior] } أصبح مخططنا جاهزًا للاستخدام مع البرمجية الوسيطة GraphQL HTTP، لذا سنمرره للدالة makeExecutableSchema، إحدى أدوات graphql-tools، بصفته typeDefs ليصبح قابلًا للتنفيذ. للدالة makeExecutableSchema معاملان أحدهما للمخطط والآخر لمحللات الاستجابة، وينبغي تعريفها قبل تنفيذ الدالة: typeDefs: لغة مخطط GraphQL، وهي سلسلة نصية. resolvers: محللات الاستجابة، وهي الدوال التي تُستدعى لتنفيذ حقل معين وإرجاع قيمة. اكتب تعليمات تعريف typeDefs ضمن الملف "server.js" تمامًا بعد تعليمات استيراد الاعتماديات، وأسند له قيمة نصية هي مخطط GraphQL، وفق التالي: ... const data = { warriors: [ { id: '001', name: 'Jaime' }, { id: '002', name: 'Jorah' }, ], } const typeDefs = ` type Warrior { id: ID! name: String! } type Query { warriors: [Warrior] } ` ... إذًا، فقد جهزّنا إلى الآن البيانات التي سنستعلم عنها data، وعرّفنا المخطط ضمن typeDefs، وسنعرّف في الخطوة التالية محللات الاستجابة، التي ستحدد طريقة استجابة خادمنا للطلبات الواردة. محللات الاستجابة محللات الاستجابة Resolvers هي مجموعة من الدوال البرمجية التي توّلد استجابة خادم GraphQL، ولكل محلل استجابة أربعة معاملات: obj: الكائن الأب، غير ضروري في حالتنا لأن الكائن المستخدم لدينا وحيد وهو الأب أو الجذر. args: تمرر هنا أي متغيرات وسيطة يحتاجها GraphQL. context: حقل مشترك بين جميع محللات الاستجابة، ويُخصص غالبًا لمعلومات الاتصال مع قاعدة البيانات. info: معلومات إضافية. سننشئ الآن محلل استجابة لاستعلام الجذر Query، يُرجع لطالب الاستعلام معلوماتٍ عن المحاربين warriors، وسنمرّر له مخزن البيانات في الذاكرة الذي عرّفناه سابقًا. ضِف ما يلي إلى الملف "server.js": ... const typeDefs = ` type Warrior { id: ID! name: String! } type Query { warriors: [Warrior] } ` const resolvers = { Query: { warriors: (obj, args, context, info) => context.warriors, }, } ... أنشأنا بهذه التعليمات دالة محلل استجابة تدعى warriors، ترتبط بالاستعلام Query وهو نقطة دخولنا إلى خادم GraphQL، ومهمة هذه الدالة هي إرجاع بيانات المحاربين من جدول المحاربين warriors الموجود في قاعدة البيانات التي يشير إليها المعامل context، وهي في حالتنا مخزن البيانات البسيط data. إذًا، فلكل دالة محلل استجابة أربعة معاملات: obj و args و info، إضافةً إلى context، الذي كان أكثرها ارتباطًا بمخططنا، وهذا المعامل مشترك بين جميع محللات الاستجابة، ويشير غالبًا إلى إعدادات الاتصال بين خادم GraphQL وقاعدة البيانات. الآن، بعد أن عرّفنا typeDefs و resolvers معاملات الدالة makeExecutableSchema، يمكننا تحويل المخطط إلى مخطط قابل للتنفيذ بكتابة الأوامر التالية في الملف "server.js": ... const resolvers = { Query: { warriors: (obj, args, context, info) => context.warriors, }, } const executableSchema = makeExecutableSchema({ typeDefs, resolvers, }) ... تنشئ الدالة makeExecutableSchema مخططًا كاملًا يمكنك تمريره إلى نقطة وصل GraphQL. لنستبدل نقطة الوصل الحالية أي عبارة الترحيب !Hello, GraphQL بنقطة الوصول graphql/ عبر كتابة التعليمات التالية: ... const executableSchema = makeExecutableSchema({ typeDefs, resolvers, }) app.use( '/graphql', graphqlHTTP({ schema: executableSchema, context: data, graphiql: true, }) ) ... يتطلب استخدام البرمجية الوسيطة graphqlHTTP مخططًا أنشأناه وسياقًا يمثّل معلومات الاتصال مع مخزن البيانات، وينص الاتفاق على استخدام خادم GraphQL نقطة الوصول graphql/. إليك المحتوى النهائي للملف "server.js" بعد جميع التعديلات: import express from 'express' import cors from 'cors' import { graphqlHTTP } from 'express-graphql' import { makeExecutableSchema } from '@graphql-tools/schema' const app = express() const port = 4000 // مخزن البيانات في الذاكرة const data = { warriors: [ { id: '001', name: 'Jaime' }, { id: '002', name: 'Jorah' }, ], } // المخطط const typeDefs = ` type Warrior { id: ID! name: String! } type Query { warriors: [Warrior] } ` // محلل الاستجابة للمحاربين const resolvers = { Query: { warriors: (obj, args, context) => context.warriors, }, } const executableSchema = makeExecutableSchema({ typeDefs, resolvers, }) app.use(cors()) app.use(express.json()) app.use(express.urlencoded({ extended: true })) // نقطة الدخول app.use( '/graphql', graphqlHTTP({ schema: executableSchema, context: data, graphiql: true, }) ) app.listen(port, () => { console.log(`Running a server at http://localhost:${port}`) }) احفظ التغييرات على الملف وأغلقه. تكتمل بذلك واجهة برمجة التطبيقات، يمكنك الآن فتح الرابط "http://localhost:4000/graphql"، واستعراض مخطط GraphQL وتصحيح أخطائه باستخدام بيئة التطوير المتكاملة GraphiQL IDE، وهو موضوع القسم التالي من المقال. استخدام GraphiQL IDE الخطوة الأولى لاستخدام بيئة التطوير GraphiQL IDE هي تفعيلها بإسناد القيمة true للخيار graphiql في كتلة التعليمات الخاصة بالبرمجية الوسيطة ضمن ملف الخادم، ولو رجعت للتعليمات السابقة ستجدها مُفعلة. تعمل GraphiQL ضمن المتصفح، وتفيدك في كتابة استعلامات GraphQL، والتحقق من صحتها، واختبارها لضمان خرج صحيح من الخادم. تنقسم شاشة GraphiQL إلى قسمين، القسم الأيسر لكتابة التعليمات والأيمن لعرض النتائج. سنجري الآن استعلامًا عن المحاربين warriors، لذلك اطلب المُعرّف id والاسم name لكل محارب بكتابة التعليمات التالية في القسم الأيسر من الشاشة: { warriors { id name } } اضغط بعدها على الزر التشغيل أعلى يسار الشاشة، وستظهر نتيجة استعلامك في القسم الأيمن بصيغة JSON، وفق التالي: Output{ "data": { "warriors": [ { "id": "001", "name": "Jaime" }, { "id": "002", "name": "Jorah" } ] } } حاول تعديل الاستعلام، استعلم مثلًا عن الاسم name فقط: { warriors { name } } ولاحظ تغير النتيجة تبعًا لاستعلامك، فهي تشمل الآن أسماء المحاربين دون أرقامهم: Output{ "data": { "warriors": [{ "name": "Jaime" }, { "name": "Jorah" }] } } تعكس هذه التجربة واحدةً من أقوى خصائص GraphQL، وهي أنها موجهة بطلبات العميل [Client-driven]()، إذ تسمح له بالاستعلام عن الحقول التي يحتاجها فقط، وتعرضها هي بالذات دون زيادة أو نقصان. تفيدك GraphiQL أيضًا باستعراض تفاصيل المخطط، فكل ما تحتاجه هو الضغط على زر التوثيق Docs في يمين الشاشة، وسيظهر لك شريط جانبي خاص بالتوثيقات. لننتقل إلى القسم الأخير من مقالنا، وفيه محاكاة لتقديم طلبات استعلام فعلية من العميل إلى واجهة GraphQL API. إرسال طلب استعلام لواجهة GraphQL API من طرف العميل يمكنك إرسال طلبات الاستعلام إلى واجهة GraphQL عبر بروتوكول HTTP بالطريقة نفسها المتبعة مع واجهات REST، وتستطيع أيضًا استعمال واجهات APIs المُضمّنة في المتصفحات مع GraphQL، ومن أمثلتها fetch الخاصة بجلب البيانات. افتح الملف "index.html"، وأنشئ بداخله هيكل HTML مع الوسم <pre> كما يلي: <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>GraphQL Client</title> </head> <pre><!-- البيانات ستعرض هنا --></pre> <body> <script> // Add query here </script> </body> </html> ثم أنشئ دالة غير متزامنة تحت الوسم script، ترسل طلب POST لواجهة GraphQL API: ... <body> <script> async function queryGraphQLServer() { const response = await fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: '{ warriors { name } }', }), }) const data = await response.json() // إضافة البيانات إلى الوسم const pre = document.querySelector('pre') pre.textContent = JSON.stringify(data, null, 2) // JSON بصيغة } queryGraphQLServer() </script> </body> ... أسند القيمة application/json للحقل Content-Type في ترويسة المقطع script، واكتب الاستعلام الذي تطلبه بصيغة سلسلة نصية ضمن المتن، فيكون تسلسل الإجراءات كما يلي: يستدعي script الدالة غير المتزامنة، فترسل طلبًا للخادم، وعند ورود الاستجابة توضع تحت الوسم pre. إليك المحتوى النهائي للملف "index.html": <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>GraphQL</title> </head> <pre></pre> <body> <script> async function queryGraphQLServer() { const response = await fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: '{ warriors { name } }', }), }) const data = await response.json() const pre = document.querySelector('pre') pre.textContent = JSON.stringify(data, null, 2) // Pretty-print the JSON } queryGraphQLServer() </script> </body> </html> احفظ التغييرات، وأغلق الملف "index.html"، ثم استعرضه بواسطة المتصفح، وافتح نافذة الشبكة Network ضمن أدوات المطور لترى الطلب المرسل عبر الشبكة من المتصفح إلى نقطة وصول GraphQL ذات العنوان "http://localhost:4000/graphql"، وافحص بعدها استجابة الخادم؛ فإذا تضمنت البيانات التي استعلمت عنها مع رمز الاستجابة 200، فاستعلامك صحيح وقد نجحت بإنشاء خادم GraphQL API. الخاتمة تناول هذا المقال إعداد خادم GraphQL API في بيئة Node.js باستخدام إطار العمل Express بطريقة مبسطة تعينك على فهم أساسيات GraphQL، وأبرزها أن الخادم يتكون من نقطة وصول وحيدة graphql/ تستقبل جميع طلبات الاستعلام، وواجهة برمجة التطبيقات تحتوي دومًا مخطط ومحلل استجابة. يتضمن المخطط نوعين، نوعًا أساسيًا للاستعلام Query، ونوعًا مخصصًا تُعرّفه حسب حالتك، وفي مثالنا كان نوع المحارب Warrior، ومهمة محلل الاستجابة جلب البيانات المناسبة لهذه الأنواع من مخزن البيانات. نأمل أننا ساعدناك في خطوتك الأولى مع GraphQL، وننصحك بتوسيع بحثك عنها لتتعرف على خياراتها المتقدمة ومكملاتها، مثل أدوات المصادقة والأمان والتخبئة caching، لتزيد فعاليتها في تطبيقاتك. ترجمة -وبتصرف- للمقال How To Set Up a GraphQL API Server in Node.js لصاحبته Tania Rascia. اقرأ أيضًا المقال السابق: مقدمة إلى GraphQL مقدمة إلى Node.js كتابة أول برنامج في بيئة Node.js وتنفيذه مدخل إلى المكتبة GraphQL واستعمالاتها في بناء تطبيقات الويب الحديثة النسخة الكاملة لكتاب البرمجة باستخدام Node.js