مقدمة عن GraphQL

م

بسم الله الرحمن الرحيم

 

محتويات المقال:

  1.   مقدمة.
  2.  GraphQL vs REST.
  3.  مفاهيم أساسية في GraphQL .
  4.  تعريف ال Type و Input.
  5.  تعريف ال resolvers.
  6.  عمل الـ Schema.
  7.  ربط Schema مع Express .
  8. تجربة التطبيق.
  9.  مصادر وتحميل المشروع.

 

  • مقدمة

GraphQL اصبحت مدرجة ضمن العديد من الاشياء التي يتحدث عنها ويستخدمها العديد من مطوري البرمجيات دعونا نتعرف سوياً ماهي GraphQL وماهي فائدتها ولماذاً اصلاً قد أحتاج إلى استخدامها.

  • ماهي GraphQL ؟
    GraphQL هي لغة استعلام , نمط ووسيلة تواصل بين الـclient والـserver تمكنك عن طريق استخدامها من البحث و إضافة وتعديل وحذف بيانات عن طريق الAPI الخاصة بك.
  • ماهي اللغات المدعومة او التي يمكنني استخدامها وتضمين GraphQL معها؟
    يمكنك التعامل مع GraphQL ودمجها مع اي مشروع تعمل عليه فهي ليست مقيدة للغة او تكنولوجيا او حتى قاعدة بيانات معينة.
    يمكنك الاطلاع على المكتبات الخاصة بها من هنا .
  • ماهي مميزات GraphQL ولماذا قد احتاج الا استخدامها عِوضاً عن REST ؟
      • – Ask Only for what you need – اطلب البيانات التي تحتاجها فقط فبدلاً من الحصول على هيكل ثابت وكامل من البيانات مع GraphQL يكون الموضوع اكثر مرونة فيمكنك طلب الحقول التي تريد فقط. فمثلاً لو لدي مقالة وبها حقول ثابتة وهي العنوان و المحتوى والتاريخ والكاتب والوسوم الدلالية وفي واجهة موقعي او تطبيقي انا احتاج فقط عنوان وكاتب المقالة ولا احتاج اي بيانات اخرى فسيكون من الافضل لي طلب هذين الحقلين فقط وعدم ارسال بقيت الحقول من السيرفر لاني لا أحتاجها ولن استخدمها.
      • – Strongly Typed –  يوجد وصف دقيق لانواع البيانات وهيكلها فلن تحصل على نوع غير الذي قمت بتحديده مسبقاً.
      • الNesting في GraphQL يمكنك عمل nested query والحصول على بيانات مرتبطة بالRequest وارجاع كل البيانات في Response واحد وفي حالة انك طلبتها فقط, سنوضحها أكثر بمثال لاحقاً.

 

  • GraphQL vs REST
    • – مقارنة بREST هل الفرق كبير؟
      – – REST و GrahpQL مختلفان كلياً وكلاهما يتبع اسلوب او نمط مختلف ففي REST انت تحدد endpoint لكل عملية مثل جلب جميع البيانات, جلب بيان واحد, تحديث , حذف.
      انما في GraphQL يوجد لديك endpoint وحيدة تتواصل وتقوم بكل العمليات من خلالها ومحتوى هذه العملية نفسه يختلف.
      في GraphQL يمكن تضمين العديد من الاشياء والحصول عليها في Request واحد بعكس REST قد تحتاج للقيام بالعديد من الRequests للحصول على البيانات التي تريدها.
    • – إذاً هل GraphQL أفضل من REST وهل يجب ان اتوقف عن استخدام REST فوراً والتوجه لGraphQL ؟
      — بالتأكيد لا, كل طريقة لها مميزاتها وعيوبها عليك أن تقرأ أكثر عن النمطين وتفهم مميزات وعيوب كل طريقة وتختار الطريقة الانسب والأسهل لمشروعك.
    • – هل يمكنني استخدام GraphQL و REST في آنٍ واحد؟
      — نعم يمكنك العمل بهما في نفس المشروع بدون اي مشاكل تذكر.

 

 

مفاهيم أساسية لGraphQL :

في البداية, العمليات في GraphQL تنقسم لقسمين اساسيين هما Queries و Mutations.

الQueries هي من اسمها تمثل الإستعلامات التي يتم فيها التواصل بين client و server للحصول على بيانات معينة.

انما ال Mutations تمثل العمليات المتعقلة بالبيانات مثل الإضافة والحذف والتعديل.

فعندما نطلب الحصول على بيانات معينة لمقالة مثلا فاننا نشئ query وعندما ننشئ او نرغب في تعديل او حذف مقالة فاننا نستخدم الmutation.

مقدمة عملية:

سنقوم بعمل مشروع صغير ليوضح الفكرة والمفاهيم الأساسية لGraphQL
سنستخدم:
Express.js وهو إطار عمل مبني على Node.js لكتابة تطبيقات للويب.

MongoDB سنستخدمها كقاعدة بيانات عن طريق مكتبة Mongoose.

Apollo Server  وهو GraphQL server يمكنك اضافته واستخدامه مع node.js واطاراتها المختلفة ويحتوي على أدوات GraphQL الأساسية التي سنحتاجها.

npm i --save express mongoose graphql apollo-server-express graphql-tools body-parser 
npm i --save-dev @babel/node @babel/preset-es2015 babel-plugin-inline-import-graphql-ast

أنا سأفترض ان لديك معرفة سابقة بكيفية استخدام Node.js و MongodDB فالموضوع هنا عن تعلم GraphQL واستخدامها!.

لنتخيل ان لدينا متجراً يحتوي على تصنيفات ومنتجات ونريد ان تكمن من إضافة و حذف وتعديل التنصيفات كبداية لنستكشف محتويات GraphQL:
أولاً: نبدأ بالتصنيف ونكتب الSchema الخاصة:
ننشئ ملف ونسميه category.model.js

import mongoose from 'mongoose'

const categoryScheam = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    description: {
        type: String
    }
})


export const Category = mongoose.model('category', categoryScheam);

 

قمنا بانشاء Schema “شكل او توصيف لشكل” المنتجات وتحتوي على حقلين وهما عنوان التصنيف ووصف لهذا التصنيف, إلى الآن الامور واضحة وبسيطة, نحتاج الآن لإنشاء ملف GraphQL لهذا الmodel وتضمين عدة اشياء به.
نحتاج لإنشاء نوع “type” لل Category لكي يتعرف الGraphQL server عليه
فماهو الType إذاً ؟
الType هو توصيف للمحتويات الخاصة بالSchema التي ننشئها بمعنى اوضح نعرف اسماء الحقول ونوعها وهي هي مطلوبة دائماً ام اختيارية
نفس فكرة الmodel في mongoose ولكن في هذه المرة لكي يتعرف GraphQL عليها.
أولاً: أنشئ ملف جديد في نفس المجلد وسمه category.graphql.

type Category {
    id: ID!
    title: String!
    description: String
}

كما نرى التعريف بسيط كل ماعلينا فعله كتابة type متبوعة بالاسم الذي نريده وبعده نفصل الحقول
الحقل المطلوب نضع بعده علامة تعجب “!” إنما الحقل الإختياري لا نضع شئ في اخره.
أنواع الحقول الأساسية المدعومة في GraphQL هي Int, String, Float, Boolean و أخيراً ID والذي يكون معرفاً فريداً للنوع.
يمكنك تعريف أنواع اخرى وسأذكر المصدر إذا كنت مهتم بها في آخر المقال.
الـType سيكون متاحاً فقط عند عمل Query لكن عند انشاء عنصر جديد او تحديث عنصر سنحتاج الى تعريف Input.
سنعرف Input لإدخال تصنيف جديد في نفس الملف ماهي الحقول التي نتوقعها من المستخدم ؟

input newCategoryInput {
    title: String!
    description: String
}

كما نرى الموضوع اصبح ابسط لدينا حقلين نتوقعمها من المستخدم هما العنوان “إجباري” والوصف “إختياري”.

تبقى لدينا شيئين لتعريفهما وهما ال Query و Mutation والذين سنستخدم فيهما الinputs و الtypes التي قمنا بتعريفها.

type Query {
    categories: [Category]!
    singleCategory(id: ID!): Category!
}

type Mutation {
    newCategory(category: newCategoryInput!): Category!
}

ملاحظة: هنا لن نكتب كود العملية نفسها بل فقط نعرف الهيكل الاساسي لها وسنقوم بكتابة اكوادها في الخطوة القادمة عن طريق ال resolvers .

قمنا بتعريف Query والذي سنحدد من خلاله الأشياء التي يمكننا الحصول عليها.
أولاً: categories والتي من خلالها سنحصل على مصفوفة من النوع Category ولن تاخذ اي مدخلات.
ثانياً: عرفنا طريقة الحصول على تصنيف وحيد والذي سياخذ ID التصنيف كمدخل ويرجع لنا عنصر واحد من نوع Category.

وقمنا بتعرف Mutation وهي ستكون مكان العمليات على البيانات سواء اضافة حذف او تعديل.
عرفنا طريقة اضافة عنصر جديد بانه سياخذ متغير اسمه category كمدخل من نوع newCategoryInput وعند ان شاء العنصر الجديد سيرجعه وسيكون من النوع الاصلي Category.
ولتحديث عنصر سنتبع نفس الطريقة.

ملاحظة: تعريف الQuery و الMutation لاول مرة سيكون تعريف مبدأئ type query و لانه مسموح لك بتعريفه مره واحدة خلال تطبيقك فعند تعريفه لmodel آخر سيكون إضافة لنفس التعريف “extend type Query” إذا لم توضح الفكرة راقب طريقة التعريف هنا وكيف سنعرفه في ال Product.

سيصبح لدينا ملف كامل بهذا الشكل:

type Category {
    id: ID!
    title: String!
    description: String
}

input newCategoryInput {
    title: String!
    description: String
}

type Query {
    categories: [Category]!
    singleCategory(id: ID!): Category!
}
type Mutation {
    newCategory(category: newCategoryInput!): Category!
}

تعريف الResolvers :

الآن حان الوقت لنكتب الـResolver فماهو ببساطة؟
هو كتابة الimplementation لتعريف  GraphQL الذي قمنا بكتابته للتو
لنبدأ
أنشئء ملف category.resolvers.js

import { Category } from './category.model'


const getAllCategories = () => Category.find({}).exec()

const getOneById = id => Category.findById(id).exec()



const insertOne = (rootValue, { category }) => Category.create(category)

export const categoryResolvers = {
    Query: {
        categories: getAllCategories,
        singleCategory: getOneById
    },
    Mutation: {
        newCategory: insertOne
    }
}

كما ترى فقد قمنا باستيراد Category Model الذي قد قمنا بتعريفه سابقاً لأننا سنستخدمه لكتابة الـ implementation لما قمنا بتحديده مسبقاً, وسنقوم بتصدير متغير يحتوي على الجزئين الأساسيين الذين تكلمنا عنهمها Query and Mutation.
أسماء الProperties بداخل كل واحدة منهما يجب ان تطابق الموجودة في ملف GraphQL الذي قمنا بتوصيفه سابقاً فمثلا اذا حددنا ان الخاصية التي سنستخدمها للحصول على جميع التصنيفات تدعى categories فلايمكنك هنا تسميتها allCategories يجب ان يتطابق الاسمين, لكن محتوى الدالة التي ستحتوي على الimplementation نفسه يمكنك كتابتها مباشرة او اسنادها لمتغير او افعل مايحلو لك فعله.

الآن وقد أنشأنا ملف الGraphQL وresolvers دعونا نجمعهما معاً لعمل schema العامة لإستخدامها.

إنشاء Schema:

قم بإنشاء schema.js

import { makeExecutableSchema } from 'graphql-tools';
import * as categoryType from './category/category.graphql'
import { categoryResolvers } from './category/category.resolvers'


export const schema = makeExecutableSchema({
    typeDefs: [
        categoryType
    ],
    resolvers: categoryResolvers
});

في الtypeDefs لو لدينا اكثر من type سنجلهم في المصفوفة الموجودة وفي الresolvers لو كان لدينا اكثر من resolver سنحتاج لدمجهم عن طريق lodash.merge ليصبحو object واحد ولكن في حالتنا لدينا resolvers لل category فقط فلانحتاجه.

 

ربط الـ Schema ب Express:

الان سنكتب الentry point لتطبيقنا server.js او index.js لايهم:

import express from 'express';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import { graphiqlExpress, graphqlExpress } from 'apollo-server-express'

import { schema } from './schema';

const app = express();

app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
mongoose.Promise = global.Promise;

mongoose.connect(`MongoDBURL`, (err) => {
  if (err) console.log(err);
  console.log('Connected TO MongoDB');
});

app.use('/graphql', graphqlExpress({
  schema: schema
}));

app.use('/docs', graphiqlExpress({ endpointURL: '/graphql' }));


app.listen(3000, () => console.log('Your app is running on PORT 3000'));

قمنا بتعريف الgraphql endpoint باستخدام graphqlExpress والتي هي  graphql server من Apollo.

وبعدها قمنا بتعريف الinteractive docs وهي عبارة واجهة تمكن من التفاعل مع queries والmutation وتجريبها.
جاهز لتجريب البرنامج ! 😀

 

تجربة التطبيق 

بعد تشغيل السيرفر نتوجه لـ “localhost:3000/docs”

الجزء الموجود في اليسار هو الجزء الذي سيمكنك الكتابة وتجريب ماقمت بانشائه في graphql server اي سيمكننا كتابة queries او انشاء عناصر جديدة او غيره.
والجزء الموجود في اليسار في الاسفل “Query Variable” سيمكنك من كتابة متغيرات في وقت انشاء او تحديث العناصر, سنتعرف عليها سوياً.
الجزء الاوسط من الشاشة هو سيظهر لنا نتائج ما قمنا بتنفذه.
الجزء الأيمن من خلال الضغط على query او mutation سيعرض لك تفاصيلها ماهي الqueries او الmutations المتوفرة لدينا وماهي المدخلات التي تقبلها.

سنقوم بعمل query لجلب كل الcategories من قاعدة البيانات وعرض الid فقط وسيكون بهذه الصورة:

{
  categories{
    id
  }
}

الquery يمكن ان يكون كما وضحنا او

query {
  categories{
    id
  }
}

او عمل اسم مخصص له 

query getAllCategories{
  categories{
    id
  }
}

بالطبع ستكون Array فارغة لإننا لم ننشئ اي عنصر بعد.
دعونا نقوم بإنشاء أول Category.

في البداية بدأنا بكلمة mutation للتعريف انه عملية وليست query وبعد ذلك اسم للmutation وهو اختياري قد تكون بدون اسم ومن خلال الparameter قمنا بتعريف متغير اسمه input وحددنا نوعه من نوع newCategoryInput وهو مطلوب كما حددنا سابقا في graphQL.
وفي داخل الmutation نستدعي الmutation الأساسية وهي تاخذ متغير اسمه category ونحدد انه هو المتغير input
وفي الأسفل في Query Variables ننشئ الvariable ونكتب القيم وبعدها ننفذ العملية وسنجد انها ارجعت لنا id , title كما حددنا. 
رائع!.

 

مصادر
هنا قد وصلنا لنهاية هذا المقال وقد كانت مقدمة بسيطة عن GraphQL لم نتطريق الى كل صغيرة وكبيرة فمازال يوجد الكثير لتعلمه.
إذا كنت مهتم بمعرفة المزيد عن GraphQL يمكنك زيارة الموقع الرسمي لهم من هنا و زيارة الموقع الرسمي الخاص بApollo .
إذا كنت ترغب في معرفة كيفية إنشاء نوع جديد للحقول من هنا.

يمكنك تحميل المشروع من هنا .
ولو كنت تبحث عن مشروع متقدم قليلاً عن هذا المشروع يمكنك استكشاف مشروعي على GitHub.

أتمنى ان تكون استفدت من هذا المقال.

عن الكاتب

أحمد مجدي

Full Stack Software Developer , Geek

4 تعليقات

اترك رداً على أحمد مجدي الغاء الرد

هذا الموقع يستخدم Akismet للحدّ من التعليقات المزعجة والغير مرغوبة. تعرّف على كيفية معالجة بيانات تعليقك.