كيفية بناء خادم GraphQL باستخدام Laravel واختباره عبر Postman

دقائق القراءة: 9

مقدمة: لماذا يُعد GraphQL خياراً عملياً لبناء API حديثة؟

تُعد GraphQL لغة استعلام مخصصة للتعامل مع واجهات API بكفاءة ومرونة أعلى من النهج التقليدي في كثير من الحالات. الفكرة الأساسية فيها أنك تطلب البيانات التي تحتاجها فقط، بالشكل الذي تريده، ومن خلال طلب واحد غالباً، بدلاً من تنفيذ عدة طلبات منفصلة كما يحدث في كثير من واجهات REST.

على سبيل المثال، إذا كنت تستخدم REST API وتريد جلب قائمة الكتب، فقد ترسل طلباً إلى GET /books/list. وإذا احتجت بعد ذلك إلى تفاصيل كتاب محدد عبر المعرّف، فسترسل طلباً آخر مثل GET /book?id={id}. هذا يعني زيادة عدد الطلبات بين العميل والخادم. أما في GraphQL، فيمكنك طلب الحقول المطلوبة بدقة والحصول على استجابة متوقعة ضمن طلب واحد، وهي ميزة تُعرف باسم declarative data fetching.

شرح عملي لبناء خادم GraphQL باستخدام Laravel واختباره عبر Postman

ما الذي ستتعلمه في هذا الدليل؟

في هذا المقال سنبني خادماً بسيطاً باستخدام حزمة Laravel GraphQL، ثم نختبره عبر أداة Postman. وسنغطي العمليات التالية:

  • تسجيل مستخدم جديد عبر Mutation.
  • جلب جميع المستخدمين.
  • جلب مستخدم محدد باستخدام المعرّف.
  • جلب جميع المنشورات.
  • جلب المستخدمين مع العلاقات المرتبطة بالمنشورات.
  • اختبار الاستعلامات والعمليات عملياً باستخدام Postman.

المتطلبات الأساسية قبل البدء

لتطبيق الخطوات بنجاح، ستحتاج إلى الأدوات التالية:

  • خادم محلي مثل XAMPP أو WAMP.
  • محرر أكواد مثل VS Code أو Sublime Text أو Atom.
  • نظام إدارة إصدارات مثل Git.
  • مدير حزم واعتمادات مثل Composer.
  • إطار العمل Laravel.
  • حزمة rebing/graphql-laravel.

أساسيات GraphQL التي يجب فهمها أولاً

ما هو GraphQL Schema؟

يمثل Schema البنية التي تصف أنواع البيانات والاستعلامات والعمليات المتاحة داخل الخادم. من خلاله يعرف العميل ما الذي يمكن طلبه، وما نوع البيانات المتوقع إرجاعها.

type User {
  id: ID!
  name: String!
  email: String!
  age: Int
  hobbies: [String]
  created_at: DateTime
  updated_at: DateTime
}

في المثال السابق، تشير علامة التعجب ! إلى أن الحقل إلزامي، كما في id وname وemail. ويمكنك أيضاً استخدام أنواع مختلفة مثل Int وString، وكذلك القوائم مثل [String].

ما هي استعلامات GraphQL Queries؟

الاستعلام في GraphQL يتيح لك طلب حقول محددة من كائن معين، وهو ما يمنحك تحكماً كبيراً في شكل البيانات المسترجعة.

{
  user {
    name
  }
}

وتكون الاستجابة المتوقعة مثلاً كالتالي:

{
  "data": {
    "user": {
      "name": "John doe"
    }
  }
}

ما هو Resolver في GraphQL؟

كل طلب بيانات داخل خادم GraphQL يحتاج إلى منطق يعالجه ويستخرج النتيجة المناسبة، وهذا ما تقوم به الدالة resolver. تستقبل هذه الدالة عادة عناصر مثل object وargs وcontext وinfo، وتستخدمها لتحديد كيفية جلب البيانات أو معالجتها.

بدء المشروع: تثبيت Laravel وGraphQL

ابدأ بإنشاء مشروع Laravel جديد عبر الطرفية:

composer create-project laravel/laravel graphql-laravel

بعد ذلك ثبّت حزمة graphql-laravel المفتوحة المصدر:

composer require rebing/graphql-laravel

وعند انتهاء التثبيت، انشر ملف الإعدادات config/graphql.php باستخدام الأمر التالي:

php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"

ثم شغّل خادم التطوير:

php artisan serve

الصفحة الرئيسية الافتراضية لمشروع Laravel بعد تشغيل خادم التطوير

إنشاء النماذج والعلاقات بين المستخدمين والمنشورات

في هذا المشروع سننشئ علاقة بين المستخدم User والمنشور Post. لإنجاز ذلك، نحتاج إلى إنشاء نموذج Post مع ملفات الهجرة والمتحكم والموارد:

php artisan make:model Post -mcr

داخل الملف app/Models/User، أضف علاقة hasMany لربط المستخدم بعدة منشورات:

public function posts()
{
    return $this->hasMany(Post::class);
}

وفي الملف app/Models/Post، أضف علاقة belongsTo لربط كل منشور بمستخدم واحد:

public function user()
{
    return $this->belongsTo(User::class);
}

إنشاء Migration لجدول المنشورات

يوفّر Laravel جدول المستخدمين افتراضياً، لذلك سننشئ فقط ملف هجرة خاصاً بجدول المنشورات:

php artisan make:migration create_post_table

بعد إنشاء الملف داخل المسار database/migrations، عرّف هيكل الجدول كما يلي:

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->integer('user_id')->unsigned();
    $table->string('title');
    $table->text('comment');
    $table->timestamps();
});

الآن حدّث ملف .env لربط التطبيق بقاعدة البيانات:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=graphql-laravel
DB_USERNAME=root
DB_PASSWORD=

ثم نفّذ أمر الترحيل لإنشاء الجداول:

php artisan migrate

إنشاء بيانات تجريبية باستخدام Factory وSeeder

لأغراض الاختبار، من المفيد إنشاء سجلات عشوائية داخل جدولي User وPost. بما أن Laravel يوفر User Factory افتراضياً، سننشئ PostFactory فقط:

php artisan make:factory PostFactory

بعد إنشاء الملف داخل database/factories، عدّل الدالة definition() كما يلي:

use Illuminate\Support\Str;

public function definition()
{
    return [
        'user_id' => rand(1, 5),
        'title' => $this->faker->name(),
        'comment' => $this->faker->realText(180)
    ];
}

ثم افتح فئة Seeder وأنشئ سجلات للمستخدمين والمنشورات:

public function run()
{
    \App\Models\User::factory(5)->create();
    \App\Models\Post::factory(5)->create();
}

بعد ذلك شغّل الأمر التالي:

php artisan db:seed

بمجرد التنفيذ، ستجد البيانات أُضيفت إلى الجداول.

جدول المنشورات بعد تعبئته ببيانات تجريبية في قاعدة البياناتجدول المستخدمين بعد إنشاء بيانات تجريبية داخل قاعدة البيانات

إنشاء نوع المستخدم UserType في GraphQL

في وقت كتابة هذا الدليل، لا توفّر الحزمة أمراً جاهزاً لإنشاء Query من الطرفية، لذا سننشئ الملفات يدوياً. أضف الكود التالي إلى الملف app/GraphQL/Type/UserType.php:

<?php

namespace App\GraphQL\Type;

use App\Models\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;

class UserType extends GraphQLType
{
    protected $attributes = [
        'name' => 'User',
        'description' => 'A user',
        'model' => User::class,
    ];

    public function fields(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::int()),
                'description' => 'The id of the user',
            ],
            'name' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The name of user',
            ],
            'email' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The email of user',
            ],
        ];
    }
}

هذا النوع يصف بنية المستخدم داخل GraphQL. لاحظ استخدام Type::nonNull(Type::string()) للدلالة على الحقول الإلزامية.

إضافة UserType إلى ملف الإعدادات

أضف النوع إلى الملف config/graphql.php:

'types' => [
    App\GraphQL\Type\UserType::class,
],

تعريف استعلام المستخدمين UsersQuery

الخطوة التالية هي تعريف استعلام يُرجع قائمة المستخدمين أو مستخدماً محدداً بناءً على معايير البحث مثل id أو name أو email.

<?php

namespace App\GraphQL\Type;

use GraphQL;
use App\Models\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;

class UserType extends GraphQLType
{
    protected $attributes = [
        'name' => 'User',
        'description' => 'A user',
        'model' => User::class,
    ];

    public function type(): Type
    {
        return Type::nonNull(Type::listOf(Type::nonNull(GraphQL::type('User'))));
    }

    public function args(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::int()),
                'description' => 'The id of the user',
            ],
            'name' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The name of user',
            ],
            'email' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The email of user',
            ]
        ];
    }

    public function resolve($root, $args)
    {
        if (isset($args['id'])) {
            return User::whereId($args['id'])->get();
        }

        if (isset($args['name'])) {
            return User::whereName($args['name'])->get();
        }

        if (isset($args['email'])) {
            return User::whereEmail($args['email'])->get();
        }

        return User::all();
    }
}

تتولى الدالة resolve() استرجاع البيانات من قاعدة البيانات. إذا وُجدت معاملات args فسيجري التصفية وفقها، وإلا ستُعاد جميع السجلات.

إضافة الاستعلام إلى ملف الإعدادات

'schemas' => [
    'default' => [
        'query' => [
            App\GraphQL\Query\UsersQuery::class,
        ],
        'mutation' => [
            // ExampleMutation::class,
        ],
        'types' => [
            // ExampleType::class,
        ],
        'middleware' => [],
        'method' => ['get', 'post'],
    ],
],

بعد ذلك ستتمكن من الوصول إلى نقطة النهاية http://localhost:8000/graphql، حيث يُعد المسار /graphql هو المسار الافتراضي للاستعلامات.

استعلام لجلب جميع المستخدمين

query {
  users {
    id
    name
    email
  }
}

نتيجة جلب جميع المستخدمين عبر GraphQL باستخدام Postman

إنشاء نوع المنشور PostType وربطه بالمستخدم

الآن ننتقل إلى تعريف نوع Post بالطريقة نفسها تقريباً:

<?php

namespace App\GraphQL\Type;

use GraphQL;
use App\Models\Post;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;

class PostType extends GraphQLType
{
    protected $attributes = [
        'name' => 'Post',
        'description' => 'A post',
        'model' => Post::class,
    ];

    public function args(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::int()),
                'description' => 'The id of the post',
            ],
            'title' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The title of post',
            ],
        ];
    }
}

هذا النوع يعرّف بيانات المنشور التي ستظهر في الاستعلامات، مثل id وtitle وcomment، ويمكن توسيعه لاحقاً لعرض العلاقة مع المستخدم.

إنشاء استعلام المنشورات PostsQuery

أضف الكود التالي إلى الملف app/GraphQL/Query/PostsQuery.php:

<?php

namespace App\GraphQL\Query;

use Closure;
use App\Models\Post;
use Rebing\GraphQL\Support\Facades\GraphQL;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;

class PostsQuery extends Query
{
    protected $attributes = [
        'name' => 'posts',
    ];

    public function type(): Type
    {
        return Type::nonNull(Type::listOf(Type::nonNull(GraphQL::type('Post'))));
    }

    public function args(): array
    {
        return [
            'id' => [
                'name' => 'id',
                'type' => Type::int(),
            ],
            'title' => [
                'name' => 'title',
                'type' => Type::string(),
            ]
        ];
    }

    public function resolve($root, $args)
    {
        if (isset($args['id'])) {
            return Post::whereId($args['id'])->get();
        }

        if (isset($args['title'])) {
            return Post::whereTitle($args['title'])->get();
        }

        return Post::all();
    }
}

كما في استعلام المستخدمين، تقوم resolve() هنا بإرجاع النتائج بناءً على المعاملات المُرسلة، أو تُعيد كل المنشورات عند غياب أي فلترة.

إضافة PostsQuery وPostType إلى الإعدادات

'schemas' => [
    'default' => [
        'query' => [
            App\GraphQL\Query\UsersQuery::class,
            App\GraphQL\Query\PostsQuery::class
        ],
        'mutation' => [
            // ExampleMutation::class,
        ],
        'types' => [
            // ExampleType::class,
        ],
        'middleware' => [],
        'method' => ['get', 'post'],
    ],
],
'types' => [
    App\GraphQL\Type\UserType::class,
    App\GraphQL\Type\PostType::class
],

استعلام لجلب جميع المنشورات

query {
  posts {
    id
    user_id
    title
    comment
  }
}

نتيجة جلب جميع المنشورات من خادم GraphQL باستخدام Postman

استعلام لجلب المستخدمين مع المنشورات المرتبطة بهم

query {
  users {
    id
    name
    posts {
      title
    }
  }
}

جلب المستخدمين مع علاقة المنشورات المرتبطة بهم عبر GraphQL

إنشاء Mutation لتسجيل مستخدم جديد

الآن سننشئ عملية Mutation لإضافة مستخدم جديد. هذا النوع من العمليات يُستخدم عندما نرغب في تغيير حالة البيانات على الخادم، مثل الإضافة أو التحديث أو الحذف.

أنشئ الملف app/Mutation/CreateUserMutation.php ثم أضف الكود التالي:

<?php

namespace App\GraphQL\Mutation;

use Closure;
use App\Models\User;
use GraphQL;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Mutation;

class CreateUserMutation extends Mutation
{
    protected $attributes = [
        'name' => 'users'
    ];

    public function type(): Type
    {
        return Type::nonNull(GraphQL::type('User'));
    }

    public function args(): array
    {
        return [
            'name' => [
                'name' => 'name',
                'type' => Type::nonNull(Type::string()),
            ],
            'email' => [
                'name' => 'email',
                'type' => Type::nonNull(Type::string()),
            ],
            'password' => [
                'name' => 'password',
                'type' => Type::nonNull(Type::string()),
            ]
        ];
    }

    public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {
        return User::firstOrCreate(
            ['email' => $args['email']],
            [
                'name' => $args['name'],
                'password' => bcrypt($args['password'])
            ]
        );
    }
}

تعتمد الدالة resolve() هنا على الأسلوب firstOrCreate() لضمان عدم تكرار المستخدمين بالبريد الإلكتروني نفسه، وهي ممارسة مفيدة للحفاظ على سلامة البيانات.

إضافة Mutation إلى ملف الإعدادات

'schemas' => [
    'default' => [
        'query' => [
            App\GraphQL\Query\UsersQuery::class,
            App\GraphQL\Query\PostsQuery::class
        ],
        'mutation' => [
            App\GraphQL\Mutation\CreateUserMutation::class,
        ],
        'types' => [],
        'middleware' => [],
        'method' => ['get', 'post'],
    ],
],

تنفيذ عملية إنشاء مستخدم جديد

mutation {
  users(name: "John Doe", email: "Johndoe@gmail.com", password: "John1234") {
    id
    name
  }
}

نتيجة تنفيذ عملية إنشاء مستخدم جديد عبر GraphQL في Postman

كيفية اختبار خادم GraphQL باستخدام Postman

بعد إعداد الخادم، يمكنك اختبار جميع الاستعلامات والعمليات من خلال Postman بسهولة. يكفي إرسال طلب POST إلى العنوان http://localhost:8000/graphql، ثم وضع الاستعلام أو العملية داخل جسم الطلب.

من أفضل مزايا Postman هنا أنه يتيح لك:

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

نصائح تقنية لتحسين بنية المشروع

  • احرص على تسمية Query وMutation بأسماء واضحة تعبّر عن وظيفتها.
  • أضف التحقق من صحة المدخلات قبل إنشاء المستخدمين أو تحديثهم.
  • استخدم العلاقات داخل النماذج بوضوح لتسهيل جلب البيانات المتداخلة.
  • فكّر في إضافة طبقة تفويض وصلاحيات إذا كان المشروع سيُستخدم في بيئة إنتاجية.
  • قلّل الحقول المعادة في الاستجابة إلى الحد الضروري لتحسين الأداء.

الخلاصة التقنية

يوفّر الجمع بين Laravel وGraphQL طريقة قوية لبناء واجهات API مرنة وسهلة التوسّع، خصوصاً عندما تكون البيانات مترابطة وتحتاج إلى استعلامات دقيقة. الحزمة rebing/graphql-laravel تجعل الدمج مع بيئة Laravel سلساً، بينما يمنحك Postman وسيلة عملية لاختبار كل من Query وMutation بسرعة. إذا كنت تبحث عن بنية حديثة تقلل الطلبات الزائدة وتمنح الواجهة الأمامية حرية أكبر في جلب البيانات، فإن هذا المسار يستحق الاعتماد عليه في مشاريعك القادمة.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *