Slash Command

/new-endpoint

Follow these steps in order. Do not skip steps or reorder them.

From laravel-api-tool-kit
Install
1
Run in your terminal
$
npx claudepluginhub ahmedesa/laravel-api-tool-kit
Details
Namespacelaravel-api-tool-kit/workflows/
Command Content

Workflow: Add a New CRUD Endpoint

Follow these steps in order. Do not skip steps or reorder them.


Step 0 — Gather Requirements

Before creating any file, confirm:

  • What is the resource name? (e.g. Car)
  • What columns does it have and what are their types?
  • Does it need soft deletes?
  • Does it need file uploads?
  • Which routes are needed? (all CRUD, or subset?)
  • Is authentication required? Which guard?

Detect the project structure first

ls app/
  • If you see Domain/DDD structure — check ls app/Domain/ to confirm folder naming conventions used in the project
  • If you see Models/, Http/ only → Standard Laravel structure

Path map — resolve once, use throughout all steps

ComponentStandard LaravelDDD
Modelapp/Models/Car.phpapp/Domain/Car/Models/Car.php
Filterapp/Filters/CarFilters.phpapp/Domain/Car/Filters/CarFilters.php
Enumapp/Enums/CarStatusEnum.phpapp/Domain/Car/Enums/CarStatusEnum.php
Actionapp/Actions/CreateCarAction.phpapp/Domain/Car/Actions/CreateCarAction.php
Policyapp/Policies/CarPolicy.phpapp/Domain/Car/Policies/CarPolicy.php
Form Requestapp/Http/Requests/Car/app/Http/Requests/Application/Car/ (or Dashboard/Car/)
Resourceapp/Http/Resources/Car/CarResource.phpapp/Http/Resources/Application/Car/CarResource.php
Controllerapp/Http/Controllers/CarController.phpapp/Http/Controllers/API/Application/Car/CarController.php

Note: Controllers, Requests, and Resources always stay in app/Http/ regardless of structure — only domain logic moves into app/Domain/.


Step 1 — Model

Follow rules/models.md for the correct structure.

  • Add all columns to $fillable
  • Add $casts for booleans, enums, and arrays
  • Add SoftDeletes if needed
  • Add relationships
  • If project uses ULID: add HasUlids trait + $keyType = 'string' + $incrementing = false
  • Do NOT bind $default_filters yet — do that after creating the Filter (Step 3)

File: app/Models/Car.php — DDD: app/Domain/Car/Models/Car.php


Step 2 — Migration

Create a standard Laravel migration:

Use the project's primary key convention from SKILL.md Project Defaults:

// ULID project
Schema::create('cars', function (Blueprint $table) {
    $table->ulid('id')->primary();
    $table->string('name');
    $table->string('color');
    $table->boolean('is_active')->default(true);
    $table->foreignUlid('user_id')->constrained()->cascadeOnDelete();
    $table->timestamps();
});

// Auto-increment project
Schema::create('cars', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('color');
    $table->boolean('is_active')->default(true);
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->timestamps();
});

Step 3 — Filter

Follow rules/filters.md for the correct structure.

  • Populate $allowedFilters with columns clients can filter by equality
  • Populate $allowedSorts with sortable columns (always include created_at)
  • Populate $columnSearch if text search is needed
  • Add $allowedIncludes for relationships clients can eager-load
  • Add custom filter methods for non-trivial conditions

File: app/Filters/CarFilters.php — DDD: app/Domain/Car/Filters/CarFilters.php

Then go back to the Model and bind it:

protected string $default_filters = CarFilters::class;

Step 4 — Enum (if needed)

Follow rules/enums.md for the correct structure. Create one for any fixed-value column (status, type, etc.).

File: app/Enums/CarStatusEnum.php — DDD: app/Domain/Car/Enums/CarStatusEnum.php

Cast it in the Model:

protected $casts = [
    'status' => CarStatusEnum::class,
];

Use it in validation:

'status' => ['required', Rule::in(CarStatusEnum::values())],

Step 5 — Form Requests

Follow rules/requests.md for the correct structure.

  • Create request: required rules
  • Update request: same rules but with sometimes prefix for partial updates

Files:

Standard: app/Http/Requests/Car/CreateCarRequest.php
          app/Http/Requests/Car/UpdateCarRequest.php

DDD:      app/Http/Requests/Application/Car/CreateCarRequest.php
          app/Http/Requests/Application/Car/UpdateCarRequest.php
          (use Dashboard/Car/ for admin-facing requests)

Step 6 — Resource

Follow rules/resources.md for the correct structure.

  • Add all scalar fields to the array
  • Use $this->whenLoaded('relation') for every relationship
  • Use dateTimeFormat() for all timestamps
  • Never add raw DB queries inside toArray()

File: app/Http/Resources/Car/CarResource.php — DDD: app/Http/Resources/Application/Car/CarResource.php


Step 7 — Policy (if authorization needed)

Follow rules/authorization.md. Create a Policy if any endpoint needs ownership or role checks.

Include all applicable methods: viewAny, view, create, update, delete.

File: app/Policies/CarPolicy.php — DDD: app/Domain/Car/Policies/CarPolicy.php

Register in AppServiceProvider or via automatic discovery (Laravel auto-discovers policies by convention).


Step 8 — Action (only if needed)

Follow rules/actions.md for the correct structure. Create one only if the operation:

  • Has multiple distinct steps (even if all on the same model)
  • Calls an external service (file upload, email, payment)
  • Needs to be reused across multiple controllers

If it's a simple Model::create($data) — do it directly in the controller. No Action needed.

For external 3rd-party integrations (SMS, payment, etc.), see rules/services.md.

File: app/Actions/CreateCarAction.php — DDD: app/Domain/Car/Actions/CreateCarAction.php


Step 9 — Controller

Follow rules/controllers.md for the correct structure. Use the pattern with a constructor-injected Action/Service/Repository if you created one in Step 8.

  • Add $this->authorize() on methods that need policy checks
  • Apply middleware at the route level (not in the constructor — deprecated in Laravel 12)

File: app/Http/Controllers/CarController.php — DDD: app/Http/Controllers/API/Application/Car/CarController.php


Step 10 — Language File

Since all user-facing strings MUST use trans(), create translation keys before writing messages in the controller.

// lang/en/car.php
return [
    'created' => 'Car created successfully.',
    'updated' => 'Car updated successfully.',
    'deleted' => 'Car deleted successfully.',
];

Step 11 — Route

Register the resource route in routes/api.php:

// Full CRUD
Route::apiResource('cars', CarController::class)->middleware('auth:sanctum');

// Or manual registration for partial CRUD
Route::middleware('auth:sanctum')->group(function () {
    Route::get('cars', [CarController::class, 'index']);
    Route::post('cars', [CarController::class, 'store'])->middleware('throttle:10,1');
    Route::get('cars/{car}', [CarController::class, 'show']);
    Route::put('cars/{car}', [CarController::class, 'update']);
    Route::delete('cars/{car}', [CarController::class, 'destroy']);
});

Step 12 — Factory & Test (recommended)

Create a factory:

// database/factories/CarFactory.php
public function definition(): array
{
    return [
        'name'      => fake()->word(),
        'color'     => fake()->safeColorName(),
        'is_active' => true,
        'user_id'   => User::factory(),
    ];
}

Write a feature test covering index, store, show, update, destroy.


Checklist

Before marking the feature done, verify (paths per Step 0 path map):

  • $fillable is populated
  • $casts covers all booleans and enums
  • Filter class has meaningful $allowedFilters and $allowedSorts
  • All controller methods use $request->validated()
  • All controller messages use trans()
  • All resource timestamps use dateTimeFormat()
  • All resource relationships use whenLoaded()
  • Routes have appropriate middleware (auth, throttle)
  • declare(strict_types=1) on every new file
  • Every method has parameter types and return types
Stats
Stars525
Forks54
Last CommitMar 20, 2026