پرش به مطلب اصلی

راهنمای ساخت CRUD پایه برای API

در این راهنما فقط از الگوهای عمومی Laravel و Doravel استفاده می‌کنیم و وارد پیاده‌سازی‌های سفارشی یک پروژه خاص نمی‌شویم.


این راهنما چه چیزی را پوشش می‌دهد؟

فرض می‌کنیم قرار است برای موجودیت Post این endpointها را بسازیم:

  • GET /api/posts
  • POST /api/posts
  • GET /api/posts/{post}
  • PUT /api/posts/{post}
  • DELETE /api/posts/{post}

روند کار در این راهنما به این ترتیب است:

  1. تحلیل DB
  2. ساخت migration دیتابیس
  3. ساخت model با reliese/laravel
  4. آماده‌سازی model
  5. پیکربندی Dorapi
  6. ساخت FormRequest
  7. ساخت JsonResource
  8. تعریف routeهای CRUD
  9. پیاده‌سازی controller و logic

پیش‌نیازها

قبل از شروع، بهتر است این موارد در پروژه آماده باشند:

  • Doravel نصب شده باشد
  • پروژه از نوع API باشد
  • تنظیمات dornica-api-kit.php منتشر شده باشد
  • ابزار تولید مدل با Reliese در پروژه فعال باشد

برای مطالعه تکمیلی:


۱. تحلیل DB

اولین مرحله کدنویسی نیست. در ابتدا باید دقیق مشخص شود چه چیزی قرار است ساخته شود.

وقتی task را تحویل می‌گیرید، این سؤال‌ها را مشخص کنید:

  • نام موجودیت چیست؟
  • جدول دیتابیس آن وجود دارد یا نه؟
  • این موجودیت چه فیلدهایی دارد؟
  • کدام فیلدها در لیست نمایش داده می‌شوند؟
  • کدام فیلدها در create و edit قابل دریافت هستند؟
  • آیا relation دارد؟
  • آیا نیاز به soft delete دارد؟
  • آیا فیلدی مثل is_active، sort، status یا created_by دارد؟

خروجی این مرحله

در پایان این مرحله باید حداقل این تصویر اولیه روشن باشد:

بخشمقدار نمونه
نام موجودیتPost
نام جدولposts
فیلدهای اصلیtitle, slug, body, is_active
relationهابرای نمونه author
فیلدهای listtitle, slug, is_active, created_at
فیلدهای create و edittitle, slug, body, is_active
هشدار

اگر این مرحله مبهم بماند، معمولاً بقیه مراحل هم با چند بار بازنویسی، تغییر migration و اصلاح validation همراه می‌شوند.


۲. migration

اگر جدول هنوز وجود ندارد، در این مرحله migration آن را ایجاد کنید.

php artisan make:migration create_posts_table

نمونه:

database/migrations/xxxx_xx_xx_create_posts_table.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('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug')->unique();
$table->text('body')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->softDeletes();
});
}

public function down(): void
{
Schema::dropIfExists('posts');
}
};
اطلاع

در این مرحله روی این موارد حساس باشید:

  • نوع هر ستون درست انتخاب شود
  • unique بودن فیلدهایی مثل slug از همین‌جا مشخص شود
  • nullable بودن یا نبودن فیلدها روشن باشد
  • اگر قرار است حذف نرم داشته باشید، softDeletes() را اضافه کنید
نکته

اگر migration از قبل وجود داشت، migration جدید نسازید. فقط migration موجود را بخوانید و مطمئن شوید ساختار جدول با task هماهنگ است.


۳. model

بعد از مشخص شدن جدول، model را بر اساس ساختار واقعی دیتابیس بسازید. در این راهنما، مبنا استفاده از Reliese است.

پکیج مرجع:

نمونه دستور:

php artisan code:models --table=posts

این دستور معمولاً model اولیه را بر اساس ساختار جدول تولید می‌کند.

اطلاع

model فقط یک فایل ساده نیست. این فایل نقطه اتصال این بخش‌ها است:

  • جدول دیتابیس
  • castها
  • relationها
  • رفتارهای Eloquent
نکته

اگر model از قبل وجود داشت، دوباره آن را نسازید. همان فایل موجود را بررسی و تکمیل کنید.


۴. آماده‌سازی model

بعد از ساخته شدن model، کار تمام نشده است. در این مرحله باید model را برای استفاده در API و به‌ویژه endpoint لیست آماده کنید.

در Doravel، قابلیت Dorapi برای endpointهای list استفاده می‌شود. یعنی هرجا قرار است query لیست، filter، sort، field و populate استاندارد داشته باشید، model باید از Dorapi پشتیبانی کند.

برای مطالعه کامل:

نمونه model:

app/Models/Post.php
<?php

namespace App\Models;

use App\Dorapi\PostDorapi;
use Dornica\APIKit\Dorapi\HasDorapi;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
use HasDorapi;
use SoftDeletes;

protected mixed $dorapi = PostDorapi::class;

protected $fillable = [
'title',
'slug',
'body',
'is_active',
];

protected $casts = [
'is_active' => 'boolean',
];
}
اطلاع

در این مرحله این بخش‌ها باید کامل شوند:

  • HasDorapi روی model
  • property مربوط به $dorapi
  • fillable
  • casts
  • relationهای لازم، اگر وجود دارند

اگر relation دارید، آن را در model تعریف کنید. مثلاً اگر هر Post به یک User تعلق دارد:

public function author()
{
return $this->belongsTo(User::class, 'author_id');
}
نکته

relation باید در model تعریف شده باشد تا بعداً در کلاس پیکربندی Dorapi قابل استفاده باشد.


۵. پیکربندی Dorapi

در این مرحله باید کلاس پیکربندی Dorapi را بسازید. این کلاس مشخص می‌کند endpoint لیست چه قرارداد و چه قابلیت‌هایی دارد.

می‌توانید آن را با command خود Doravel بسازید:

php artisan dornica:make-dorapi Post

نمونه:

app/Dorapi/PostDorapi.php
<?php

namespace App\Dorapi;

use Dornica\APIKit\Dorapi\Builders\Field;
use Dornica\APIKit\Dorapi\Builders\Relation;
use Dornica\APIKit\Dorapi\Builders\Sort;
use Dornica\APIKit\Dorapi\Dorapi;
use Dornica\APIKit\Dorapi\DorapiProperty;
use Dornica\APIKit\Dorapi\Enums\FilterOperator;
use Dornica\APIKit\Dorapi\Enums\SortDirection;

class PostDorapi extends Dorapi
{
public function fields(): Field
{
return DorapiProperty::field()
->addDefault('title')
->addDefault('slug')
->addDefault('is_active')
->addDefault('created_at')
->add('body');
}

public function relations(): Relation
{
return DorapiProperty::relation()
->add('author');
}

public function filters(): array
{
return [
DorapiProperty::filter()
->make('id')
->allowedOperators([
FilterOperator::EQUAL,
FilterOperator::IN,
]),

DorapiProperty::filter()
->make('title')
->allowedOperators([
FilterOperator::EQUAL,
FilterOperator::LIKE,
]),

DorapiProperty::filter()
->make('is_active')
->allowedOperators([
FilterOperator::EQUAL,
]),
];
}

public function sorts(): Sort
{
return DorapiProperty::sort()
->add('title')
->add('created_at')
->addDefault('id', SortDirection::DESC);
}
}
اطلاع

این فایل این موارد را کنترل می‌کند:

  • fieldهای قابل انتخاب
  • relationهای قابل populate
  • filterهای مجاز
  • sortهای مجاز
  • sort پیش‌فرض
هشدار

هر چیزی که در لیست لازم است، باید در این کلاس تعریف شود. اگر فیلدی اینجا تعریف نشده باشد، نباید انتظار داشته باشید کاربر بتواند روی آن filter بزند، sort بزند یا آن را در fields درخواست کند.


۶. FormRequest

در این مرحله باید validation عملیات create و edit را بنویسید. بهترین محل این کار در Laravel، FormRequest است.

ساخت فایل‌ها

php artisan make:request StorePostRequest
php artisan make:request UpdatePostRequest

Store request

app/Http/Requests/StorePostRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}

public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'slug' => ['required', 'string', 'max:255', 'unique:posts,slug'],
'body' => ['nullable', 'string'],
'is_active' => ['required', 'boolean'],
];
}
}

Update request

app/Http/Requests/UpdatePostRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class UpdatePostRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}

public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'slug' => [
'required',
'string',
'max:255',
Rule::unique('posts', 'slug')->ignore($this->route('post')),
],
'body' => ['nullable', 'string'],
'is_active' => ['required', 'boolean'],
];
}
}
هشدار

اگر validation را از ابتدا درست ننویسید:

  • داده خراب وارد دیتابیس می‌شود
  • رفتار create و edit غیرقابل پیش‌بینی می‌شود
  • خطاهای API ساختار روشنی نخواهند داشت

۷. JsonResource

در بسیاری از پروژه‌ها، endpoint لیست با Dorapi مدیریت می‌شود و endpointهای غیر list مانند:

  • show
  • گاهی store
  • گاهی update

با JsonResource خروجی تمیز و کنترل‌شده‌تری می‌گیرند.

اگر نیاز دارید خروجی show یا سایر endpointهای غیر list کنترل شود، resource بسازید:

php artisan make:resource PostResource

نمونه:

app/Http/Resources/PostResource.php
<?php

namespace App\Http\Resources;

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

class PostResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'body' => $this->body,
'is_active' => $this->is_active,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
نکته

اگر خروجی endpoint خیلی ساده است و نیازی به mapping یا ساختاردهی خاص ندارد، می‌توانید مستقیماً model را برگردانید. اما در بیشتر پروژه‌ها، استفاده از resource انتخاب تمیزتر و قابل نگه‌داری‌تری است.


۸. routeها

در این مرحله routeها را تعریف کنید. در این راهنما routeها را به صورت صریح می‌نویسیم تا ساختار کار برای توسعه‌دهنده کاملاً روشن باشد.

routes/api.php
use App\Http\Controllers\Api\PostController;
use Illuminate\Support\Facades\Route;

Route::prefix('posts')->as('posts.')->controller(PostController::class)->group(function () {
Route::get('/', 'index')->name('index');
Route::post('/', 'store')->name('store');

Route::prefix('{post}')->group(function () {
Route::get('/', 'show')->name('show');
Route::put('/', 'update')->name('update');
Route::delete('/', 'destroy')->name('destroy');
});
});
اطلاع

این routeها endpointهای زیر را می‌سازند:

  • GET /api/posts
  • POST /api/posts
  • GET /api/posts/{post}
  • PUT /api/posts/{post}
  • DELETE /api/posts/{post}
نکته

تعریف صریح routeها برای توسعه‌دهنده مبتدی خواناتر است، دنبال کردن flow را ساده‌تر می‌کند و نام actionها را شفاف نگه می‌دارد.


۹. controller و logic

در این مرحله همه قطعه‌ها آماده‌اند. اکنون باید controller را بسازید و logic هر action را کامل کنید.

ساخت controller

php artisan make:controller Api/PostController

نمونه controller

app/Http/Controllers/Api/PostController.php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
use App\Http\Resources\PostResource;
use App\Models\Post;
use Illuminate\Support\Facades\Response;

class PostController extends Controller
{
public function index()
{
$posts = Post::query()->dorapi()->dorapiPaginate();

return Response::success(
message: 'لیست پست‌ها با موفقیت دریافت شد.',
data: $posts,
hasPagination: true,
);
}

public function store(StorePostRequest $request)
{
$post = Post::create($request->validated());

return Response::store(
message: 'پست با موفقیت ایجاد شد.',
data: [
'id' => $post->id,
],
);
}

public function show(Post $post)
{
return Response::success(
message: 'اطلاعات پست با موفقیت دریافت شد.',
data: new PostResource($post),
);
}

public function update(UpdatePostRequest $request, Post $post)
{
$post->update($request->validated());

return Response::update(
message: 'پست با موفقیت به‌روزرسانی شد.',
);
}

public function destroy(Post $post)
{
$post->delete();

return Response::destroy(
message: 'پست با موفقیت حذف شد.',
);
}
}

منطق actionها

index

  • query مدل را می‌گیرد
  • dorapi() را روی آن اجرا می‌کند
  • dorapiPaginate() را صدا می‌زند
  • پاسخ استاندارد لیست را برمی‌گرداند

store

  • داده را از StorePostRequest می‌گیرد
  • داده اعتبارسنجی‌شده را ذخیره می‌کند
  • شناسه رکورد ایجادشده را برمی‌گرداند

show

  • رکورد را از route model binding دریافت می‌کند
  • در صورت نیاز آن را با JsonResource برمی‌گرداند

update

  • داده را از UpdatePostRequest می‌گیرد
  • آن را روی model اعمال می‌کند
  • پاسخ موفق update را برمی‌گرداند

destroy

  • رکورد را حذف می‌کند
  • پاسخ موفق delete را برمی‌گرداند
اطلاع

در یک CRUD پایه، بهتر است مسئولیت هر action روشن و محدود بماند. controller باید flow عملیات را مدیریت کند، validation در FormRequest انجام شود و تنظیمات endpoint لیست نیز در کلاس Dorapi باقی بماند.


چک‌لیست نهایی

قبل از تمام شدن کار، این موارد را چک کنید:

  1. task و فیلدهای دیتابیس را درست تحلیل کرده‌اید
  2. migration صحیح است
  3. model با Reliese ساخته یا بازبینی شده است
  4. model برای Dorapi آماده شده است
  5. کلاس پیکربندی Dorapi کامل شده است
  6. validation create و edit نوشته شده است
  7. اگر لازم بوده JsonResource ساخته شده است
  8. routeهای CRUD ثبت شده‌اند
  9. logic controller کامل شده است