تطوير الويب

إنشاء API REST بـ Laravel 11 خطوة بخطوة

4 min de lecture

📍 الدليل المرجعي: Laravel 11 وPHP 8.4

API REST مُصَمَّمة جيّدًا أساس أيّ مشروع حديث: تطبيقات محمولة، SPAs، تكاملات خارجية، microservices — كلّها تستهلك endpoints HTTP مُهَيكَلَة. Laravel 11 يجعل إنشاء API نظيفًا بفضل routes opt-in، controllers ressource، Form Requests للتحقّق، وAPI Resources لتسلسل ردود JSON. يبني هذا الدليل API كاملة لإدارة مقالات مدوّنة، خطوة بخطوة.

المتطلّبات

  • PHP 8.2 كحدّ أدنى (8.4 موصى به).
  • Laravel 11 مثبَّت.
  • قاعدة بيانات مُهَيَّأة (MySQL، PostgreSQL، أو SQLite).
  • Postman، Insomnia، أو curl لاختبار endpoints.
  • الوقت: 60 إلى 90 دقيقة.

الخطوة 1 — تفعيل routing API في Laravel 11

في Laravel 11، ملفّ routes/api.php لا يُنشَأ افتراضيًّا. لتفعيل routing API وتثبيت Sanctum:

php artisan install:api

هذا الأمر يُنشئ routes/api.php، يُثَبِّت Sanctum، ينشر migrations وينفّذها. كلّ routes في routes/api.php تستقبل تلقائيًّا بادئة /api وmiddleware api.

php artisan route:list

الخطوة 2 — إنشاء النموذج، migration، وcontroller

php artisan make:model Article -msc --api

هذا يُنشئ app/Models/Article.php، migration، seeder، وcontroller API بـ index/store/show/update/destroy. عَرِّف schema في migration:

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->string('slug')->unique();
            $table->enum('status', ['draft', 'published'])->default('draft');
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->timestamp('published_at')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('articles');
    }
};
php artisan migrate

الخطوة 3 — تهيئة نموذج Article

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;

class Article extends Model
{
    protected $fillable = [
        'title', 'content', 'slug', 'status', 'published_at'
    ];

    protected $casts = [
        'published_at' => 'datetime',
    ];

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

    protected static function booted(): void
    {
        static::creating(function (Article $article) {
            if (empty($article->slug)) {
                $article->slug = Str::slug($article->title);
            }
        });
    }
}

الـ hook booted() مع creating observer inline: مع كلّ إنشاء، إن لم يُوَفَّر slug، Laravel يُوَلِّده تلقائيًّا من العنوان.

مهمّ: لكي يعمل $user->articles()->create(...) في controller، أضف العلاقة العكسية في User:

<?php
// app/Models/User.php
use Illuminate\Database\Eloquent\Relations\HasMany;

class User extends Authenticatable
{
    public function articles(): HasMany
    {
        return $this->hasMany(Article::class);
    }
}

الخطوة 4 — التحقّق بـ Form Requests

التحقّق لا يتمّ مباشرة في controller — هذا دور Form Requests. الفصل يُبقي controller نظيفًا ويُمَركِز قواعد التحقّق.

php artisan make:request StoreArticleRequest
php artisan make:request UpdateArticleRequest
<?php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreArticleRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user() !== null;
    }

    public function rules(): array
    {
        return [
            'title'        => ['required', 'string', 'min:5', 'max:255'],
            'content'      => ['required', 'string', 'min:50'],
            'slug'         => ['nullable', 'string', 'unique:articles,slug'],
            'status'       => ['in:draft,published'],
            'published_at' => ['nullable', 'date', 'required_if:status,published'],
        ];
    }
}

إن أخفق التحقّق، Laravel يُرجع 422 Unprocessable Content مع تفصيل الأخطاء — تمامًا ما ينتظره عميل API. authorize() يُقَيَّم مسبقًا: إن أرجع false، Laravel يردّ 403.

الخطوة 5 — تنفيذ controller API

<?php
namespace App\Http\Controllers;

use App\Http\Requests\StoreArticleRequest;
use App\Http\Requests\UpdateArticleRequest;
use App\Http\Resources\ArticleResource;
use App\Models\Article;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

class ArticleController extends Controller
{
    // GET /api/articles
    public function index(): AnonymousResourceCollection
    {
        $articles = Article::with('user')->latest()->paginate(15);
        return ArticleResource::collection($articles);
    }

    // POST /api/articles
    public function store(StoreArticleRequest $request): ArticleResource
    {
        $article = $request->user()->articles()->create($request->validated());
        return new ArticleResource($article);
    }

    // GET /api/articles/{article}
    public function show(Article $article): ArticleResource
    {
        return new ArticleResource($article->load('user'));
    }

    // PUT /api/articles/{article}
    public function update(UpdateArticleRequest $request, Article $article): ArticleResource
    {
        $article->update($request->validated());
        return new ArticleResource($article);
    }

    // DELETE /api/articles/{article}
    public function destroy(Article $article): JsonResponse
    {
        $article->delete();
        return response()->json(null, 204);
    }
}

show(Article $article) يستعمل Route Model Binding: حين تستقبل route معامل {article}، Laravel يستعلم تلقائيًّا ويحقن المثيل. إن لم يوجد، 404 تلقائي.

الخطوة 6 — API Resource لتسلسل الردود

php artisan make:resource ArticleResource
<?php
namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class ArticleResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'           => $this->id,
            'title'        => $this->title,
            'slug'         => $this->slug,
            'content'      => $this->content,
            'status'       => $this->status,
            'published_at' => $this->published_at?->toIso8601String(),
            'created_at'   => $this->created_at->toIso8601String(),
            'author'       => [
                'id'   => $this->user?->id,
                'name' => $this->user?->name,
            ],
        ];
    }
}

الصياغة $this->published_at?->toIso8601String() تستعمل nullsafe في PHP: إن كان null، التعبير يُرجع null دون رفع استثناء.

الخطوة 7 — إعلان routes واختبار API

<?php
// routes/api.php

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

// Routes عامّة
Route::apiResource('articles', ArticleController::class)->only(['index', 'show']);

// Routes محمية
Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('articles', ArticleController::class)->except(['index', 'show']);
});
php artisan route:list --path=api/articles
curl -s http://localhost:8000/api/articles | jq .

الردّ كائن JSON بمفتاح data (مصفوفة المقالات) وmeta بمعلومات pagination.

أخطاء شائعة

الخطأ السبب الحلّ
404 على /api/articles routes/api.php غائب php artisan install:api
422 على POST مع بيانات صحيحة authorize() يُرجع false أرجِع true للـ routes العامّة أو تحقّق من التوثيق
user_id لا يُملأ تلقائيًّا استعمال Article::create() أنشئ دائمًا عبر $request->user()->articles()->create()
JSON يحوي حقولًا حسّاسة إرجاع النموذج مباشرة غَلِّف الكلّ في ArticleResource

FAQ

هل نستعمل دائمًا API Resources؟ نعم في الإنتاج. إرجاع النموذج مباشرة يكشف كلّ الخصائص.

كيف ننسخ API؟ ببادئة routes: Route::prefix('v1')->group(...).

الفرق بين apiResource وresource؟ resource يُولِّد 7 routes (مع create وedit). apiResource يُولِّد 5 فقط — مناسبة لـ API REST.

الأدلّة الأخرى

🔝 العودة للدليل الرئيسي

مقالات ذات صلة

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité