دليل Laravel API للمبتدئين (بناء REST API باستخدام Laravel 13)

يعطيك هذا الدليل الخطوات كاملة من البداية لبناء واجهة برمجة تطبيقات (API) في Laravel. نفّذ الأمر php artisan install:api، وأنشئ نموذجًا (Model) مع ملف ترحيل (Migration)، ثم أضف سطرًا واحدًا هو Route::apiResource() لتحصل على خمس نقاط نهاية (Endpoints). بعد ذلك تحقّق من المدخلات باستخدام طلب نموذج (Form Request)، ونسّق مخرجات JSON باستخدام مورد API (API Resource)، ثم احمِ كل شيء بالبرمجية الوسيطة (Middleware) auth:sanctum من Sanctum. أرسل طلبًا، وستصلك استجابة JSON مرتبة.

شاهد الفيديو (9 دقائق):

سنبني فيما يلي واجهة API حقيقية لتذاكر الدعم الفني باسم helpdesk-api، خطوة بخطوة في Laravel 13، مع كل أمر وكل ملف. ثم نحل الأخطاء الأربعة التي يقع فيها الجميع تقريبًا.

ما تحتاج إليه

  • PHP 8.3 أو أحدث. يتطلب Laravel 13 (صدر في 17 مارس 2026) الإصدار PHP 8.3 أو أحدث.
  • Composer ومثبّت Laravel (composer global require laravel/installer). على جهاز جديد، يثبّت موقع php.new كلًّا من PHP وComposer والمثبّت بأمر واحد.
  • عميل API مثل Postman أو Insomnia أو Bruno، لإرسال الطلبات.
  • معرفة أساسية بلغة PHP. لست بحاجة إلى معرفة Laravel مسبقًا.

ما زلت تستخدم Laravel 12؟ كل الخطوات التالية تعمل بالطريقة نفسها. لكن Laravel 12 يتلقى إصلاحات أمنية فقط حتى 24 فبراير 2027، لذلك ابدأ مشاريعك الجديدة على الإصدار 13.

الخطوة 1: أنشئ المشروع وفعّل مسارات API

laravel new helpdesk-api

يطرح المثبّت بعض الأسئلة. لمشروع API خالص، اختر None لحزمة البداية (Starter Kit)، وأبقِ SQLite قاعدةً للبيانات. لا تحتاج SQLite إلى أي إعداد: ينشئ Laravel الملف database/database.sqlite وينفّذ أول عمليات الترحيل نيابةً عنك.

وهنا النقطة التي يغفل عنها المبتدئون: تطبيق Laravel الجديد لا يحتوي على الملف routes/api.php. فعّل توجيه API بأمر واحد:

cd helpdesk-api
php artisan install:api

يقوم الأمر install:api بثلاثة أشياء:

  1. يثبّت Laravel Sanctum، وهو حارس الرموز (Token Guard) الذي سنستخدمه في الخطوة 5.
  2. ينشئ الملف routes/api.php.
  3. ينشر ملف الترحيل الخاص بجدول personal_access_tokens ويعرض عليك تنفيذه (وافق على ذلك).

كل مسار في routes/api.php عديم الحالة (Stateless)، أي لا يعتمد على الجلسات، ويحصل تلقائيًا على البادئة /api. لذلك يكون مسار tickets على العنوان /api/tickets. شغّل خادم التطوير:

php artisan serve
# INFO  Server running on [http://127.0.0.1:8000].

الخطوة 2: أنشئ نموذج Ticket وملف الترحيل

php artisan make:model Ticket -m

يُنشئ الخيار -m ملف الترحيل في الخطوة نفسها. افتح الملف database/migrations/xxxx_create_tickets_table.php وأضف الأعمدة التي تحتاجها التذكرة:

Schema::create('tickets', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('description');
    $table->string('priority')->default('medium');
    $table->string('status')->default('open');
    $table->timestamps();
});

نفّذه:

php artisan migrate

ثم افتح app/Models/Ticket.php وأضف المصفوفة $fillable:

class Ticket extends Model
{
    protected $fillable = [
        'title', 'description', 'priority', 'status',
    ];
}

المصفوفة $fillable قائمة أمان. فهي تخبر Laravel بالحقول التي يُسمح للدالتين Ticket::create() وupdate() بتعبئتها من الطلب، فلا يستطيع أحد تمرير عمود لم تقصد قبوله أصلًا.

الخطوة 3: أضف المسارات ومتحكم API

يعطيك أمر Artisan واحد المتحكم (Controller) وطلبَي نموذج:

php artisan make:controller TicketController --api --model=Ticket --requests
  • الخيار --api يتجاوز الدالتين create وedit (فواجهة API لا تحتوي على نماذج HTML).
  • الخيار --model=Ticket يحدد نوع المعامل Ticket $ticket في الدوال.
  • الخيار --requests ينشئ StoreTicketRequest وUpdateTicketRequest.

أضف الآن سطرًا واحدًا إلى routes/api.php:

use App\Http\Controllers\TicketController;
use Illuminate\Support\Facades\Route;

Route::apiResource('tickets', TicketController::class);

تحقّق مما حصلت عليه:

php artisan route:list --path=api
الطريقة العنوان (URI) الإجراء
GET /api/tickets index (عرض القائمة)
POST /api/tickets store (إنشاء)
GET /api/tickets/{ticket} show (تذكرة واحدة)
PUT/PATCH /api/tickets/{ticket} update (تعديل)
DELETE /api/tickets/{ticket} destroy (حذف)

خمس نقاط نهاية من سطر واحد. تحصل الدوال show وupdate وdestroy على التذكرة تلقائيًا عبر ربط النموذج بالمسار (Route Model Binding): يتحول الرقم 7 في /api/tickets/7 إلى التذكرة رقم 7، أو إلى استجابة 404 إذا لم تكن موجودة.

الخطوة 4: تحقّق من المدخلات ونسّق مخرجات JSON

طلبات النماذج (Form Requests)

افتح الملف app/Http/Requests/StoreTicketRequest.php. يولّد Laravel الدالة authorize() وهي تُرجع false، مما يمنع كل طلب ويعيد الخطأ 403. اجعلها تُرجع true (أو ضع فيها تحققًا حقيقيًا من الصلاحيات)، ثم أضف قواعد التحقق:

public function authorize(): bool
{
    return true;
}

public function rules(): array
{
    return [
        'title' => ['required', 'string', 'max:255'],
        'description' => ['required', 'string'],
        'priority' => ['sometimes', 'in:low,medium,high'],
    ];
}

في UpdateTicketRequest، أضف القاعدة sometimes حتى يتمكن العميل من تعديل حقل واحد فقط:

public function rules(): array
{
    return [
        'title' => ['sometimes', 'required', 'string', 'max:255'],
        'description' => ['sometimes', 'required', 'string'],
        'priority' => ['sometimes', 'in:low,medium,high'],
        'status' => ['sometimes', 'in:open,closed'],
    ];
}

أرسل طلب POST /api/tickets فارغًا (مع الترويسة Accept: application/json)، وسيرد Laravel بالرمز 422 مع رسالة message وقائمة أخطاء errors، دون أن تكتب أي جملة if في شيفرتك:

{
  "message": "The title field is required. (and 1 more error)",
  "errors": {
    "title": ["The title field is required."],
    "description": ["The description field is required."]
  }
}

موارد API (API Resources)

لا ترسل النموذج مباشرةً إلى الخارج أبدًا (سنشرح السبب لاحقًا). أنشئ موردًا:

php artisan make:resource TicketResource
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        'description' => $this->description,
        'priority' => $this->priority,
        'status' => $this->status,
        'opened_at' => $this->created_at->toDateTimeString(),
    ];
}

المتحكم في صورته النهائية

use App\Http\Requests\StoreTicketRequest;
use App\Http\Requests\UpdateTicketRequest;
use App\Http\Resources\TicketResource;
use App\Models\Ticket;

class TicketController extends Controller
{
    public function index()
    {
        return TicketResource::collection(Ticket::latest()->paginate(10));
    }

    public function store(StoreTicketRequest $request)
    {
        $ticket = Ticket::create($request->validated());

        return (new TicketResource($ticket))->response()->setStatusCode(201);
    }

    public function show(Ticket $ticket)
    {
        return new TicketResource($ticket);
    }

    public function update(UpdateTicketRequest $request, Ticket $ticket)
    {
        $ticket->update($request->validated());

        return new TicketResource($ticket);
    }

    public function destroy(Ticket $ticket)
    {
        $ticket->delete();

        return response()->noContent(); // 204
    }
}

أنشئ تذكرة:

POST /api/tickets
{
  "title": "VPN not connecting",
  "description": "Abd Almouhimen Shamout can't reach the office VPN from home.",
  "priority": "high"
}

ستحصل على الاستجابة 201 Created، والتذكرة داخل المفتاح data. أما الطلب GET /api/tickets فيُرجع القائمة داخل data، بالإضافة إلى links وmeta القادمين من أداة الترقيم (Paginator)، فتعرف واجهتك الأمامية المكتوبة بـ React أو Next.js عدد الصفحات الموجودة.

الخطوة 5: احمِ واجهة API باستخدام Sanctum

في الوضع الحالي يستطيع أي شخص حذف تذاكرك. أضف السمة (Trait) HasApiTokens إلى app/Models/User.php (يذكّرك الأمر install:api بذلك في مخرجاته، ومن السهل أن يفوتك):

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}

ضع المسارات داخل البرمجية الوسيطة auth:sanctum:

Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('tickets', TicketController::class);
});

الآن يحصل أي طلب بلا رمز على الرد 401 {"message": "Unauthenticated."}. لإنشاء رمز للاختبار، افتح Tinker:

php artisan tinker
$user = User::factory()->create(['name' => 'Abd Almouhimen Shamout', 'email' => 'abd@mouhimen.dev']);
$user->createToken('postman')->plainTextToken;
// = "1|hN3kQ8vX2pLm9..."

انسخ الرمز فورًا. يخزّن Sanctum تجزئة SHA-256 للرمز فقط، فلن تتمكن من رؤية الرمز الأصلي مرة أخرى. في عميل API، اختر Authorization > Bearer Token، والصق الرمز، ثم أرسل الطلب: ستحصل على 200 OK. في التطبيق الحقيقي، يوزّع مسار تسجيل الدخول هذا الرمز، وهذا موضوع المقال التالي في هذه السلسلة.

نصيحة من عملي مع العملاء

لا أُرجع أبدًا نموذج Eloquent مباشرةً من المتحكم. فالجداول تكبر مع الوقت: يضيف أحدهم يومًا ما عمود internal_notes أو رقم هاتف أحد الفنيين، فيرسله النموذج الخام إلى كل العملاء في اليوم نفسه دون أن يلاحظ أحد. أما المورد فيرسل الحقول التي تحددها فقط. النماذج تبقى في الداخل، والموارد تخرج إلى الخارج. وعندما تبدأ تطبيقات خارجية باستخدام واجهتك، ضع رقم الإصدار في العنوان (/api/v1/tickets) حتى تتمكن من تغيير الأمور لاحقًا دون أن تعطّل تلك التطبيقات.

الأخطاء وحلولها

1. Route [login] not defined.
استدعيت مسارًا محميًا دون رمز، ولأن الطلب لم يطلب استجابة JSON، حاول Laravel تحويلك إلى صفحة تسجيل دخول غير موجودة في واجهة API. أرسل الترويسة Accept: application/json مع كل طلب، وستحصل بدلًا من ذلك على خطأ 401 صحيح بصيغة JSON. في React أو Next.js، اضبطها مرة واحدة:

fetch('/api/tickets', {
  headers: { Accept: 'application/json', Authorization: `Bearer ${token}` },
});

ولفرض JSON على كل مسارات API من جهة الخادم أيضًا، أضف برمجية وسيطة صغيرة:

// app/Http/Middleware/ForceJson.php
public function handle(Request $request, Closure $next): Response
{
    $request->headers->set('Accept', 'application/json');

    return $next($request);
}

// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->api(prepend: [\App\Http\Middleware\ForceJson::class]);
})

2. الخطأ 403 This action is unauthorized.
ما زالت الدالة authorize() في طلب النموذج تُرجع false. اجعلها تُرجع true، أو ضع فيها تحققك الحقيقي من الصلاحيات.

3. Add [title] to fillable property to allow mass assignment on [App\Models\Ticket].
لا يحتوي النموذج على هذا الحقل في $fillable، وهو ما يمنع الإسناد الجماعي (Mass Assignment)، أي تعبئة عدة حقول دفعة واحدة من الطلب. أضف الحقل، وستعمل create() من جديد.

4. الخطأ 404 Not Found على /api/tickets.
تحقّق من ثلاثة أمور: هل نفّذت php artisan install:api؟ هل يبدأ عنوانك بـ /api؟ هل يظهر المسار عند تنفيذ php artisan route:list --path=api؟ وإذا كنت قد خزّنت المسارات مؤقتًا (Cache)، فنفّذ php artisan route:clear.

قائمة التحقق

  • نُفّذ الأمر php artisan install:api، والملف routes/api.php موجود
  • النموذج يحتوي على $fillable
  • سُجّل Route::apiResource()، ويعرض route:list خمسة مسارات
  • طلبات النماذج تُرجع true من authorize() وتحتوي على قواعد تحقق
  • المتحكم يُرجع موارد API، ولا يُرجع النماذج الخام أبدًا
  • السمة HasApiTokens مضافة إلى User، والمسارات داخل auth:sanctum
  • كل عميل يرسل الترويسة Accept: application/json

الأسئلة الشائعة

هل Laravel مناسب لبناء REST API؟
نعم. التوجيه، والتحقق من المدخلات، وموارد JSON، والترقيم، والمصادقة بالرموز كلها مدمجة فيه. لذلك يستطيع المبتدئ إطلاق واجهة CRUD آمنة في فترة ما بعد الظهر، وهي قابلة للتوسع لتطبيقات كبيرة.

ما الفرق بين Route::resource وRoute::apiResource؟
يسجّل apiResource المسارات نفسها باستثناء create وedit، وهما مخصصان لعرض نماذج HTML فقط. لواجهة API تحتاج إلى apiResource.

هل أحتاج إلى Sanctum أو Passport لواجهة API في Laravel؟
يكفي Sanctum لمعظم التطبيقات: رموز شخصية لتطبيقات الجوال والعملاء الخارجيين، ومصادقة بملفات تعريف الارتباط (Cookies) لتطبيق الصفحة الواحدة (SPA) الخاص بك. استخدم Passport فقط عندما تحتاج إلى خادم OAuth2 كامل.

لماذا تُرجع واجهة API في Laravel صفحات HTML بدلًا من JSON؟
لأن الطلب لا يرسل الترويسة Accept: application/json، فيرد Laravel كما لو كان الطلب من متصفح (تحويلات وصفحات أخطاء HTML). أضف الترويسة، أو البرمجية الوسيطة ForceJson المذكورة أعلاه.

هل يعمل هذا الدليل في Laravel 12؟
نعم. تعمل install:api وapiResource وطلبات النماذج والموارد وSanctum بالطريقة نفسها في Laravel 12 و13.

تابع التعلّم

شاهد الفيديو كاملًا:

المصادر


هذا المقال ترجمة عربية للمقال الأصلي باللغة الإنجليزية للكاتب عبد المهيمن شموط على mouhimen.dev.

اترك تعليقاً

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


الإتصال بنا

عملاء المركز التقني الكرام نتشرف بتواصلكم معنا حيث يمكن طلب خدمة او استفسار أو طلب دعم ... نفخر بثقتكم.

البريد الإلكتروني:
info@tec-c.net

إدارة و برمجة و تطوير :

عبد المهيمن محمد هشام شموط