# Laravel Architecture

> Guide Laravel 11 REST API projects using Service + Repository + DTO pattern, module-based structure, migration conventions, model relationships, API Resources, and route organization. Use when asked about Laravel project structure, where to put code, how to write migrations, how to design controllers, how to implement a new module, or how to review Laravel code for convention compliance.

- **Type:** Skill
- **Install:** `agentstack add skill-ahmmedrasel-dev-agent-skills-laravel-architecture`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [ahmmedrasel-dev](https://agentstack.voostack.com/s/ahmmedrasel-dev)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [ahmmedrasel-dev](https://github.com/ahmmedrasel-dev)
- **Source:** https://github.com/ahmmedrasel-dev/agent-skills/tree/main/skills/laravel-architecture

## Install

```sh
agentstack add skill-ahmmedrasel-dev-agent-skills-laravel-architecture
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Laravel Architecture

## When to use

Use this skill when working on a Laravel 11 REST API project that follows the Service + Repository + DTO pattern. Most useful when:

- Starting a new module (e.g. Product, Order, Category)
- Writing or reviewing migrations
- Deciding where business logic, DB queries, or validation belongs
- Reviewing controller, service, or repository code for convention compliance
- Setting up route structure with auth and role-based access

## Input parameters

- Module name or feature being built (e.g. `Product`, `Order`)
- Whether it is a new module or an existing one being modified
- Any specific layer in question (migration, model, repository, service, controller, resource)
- Auth requirements (public, authenticated, admin-only)

## Procedure

### 1. Project Structure

Every feature follows this vertical slice layout:

```
app/
├── Http/
│   ├── Controllers/Api/V1//
│   │   └── Controller.php
│   ├── Requests//
│   │   ├── StoreRequest.php
│   │   └── UpdateRequest.php
│   └── Resources//
│       ├── Resource.php
│       └── Collection.php
├── Services/
│   └── Service.php
├── Repositories/
│   ├── Interfaces/RepositoryInterface.php
│   └── Repository.php
├── DTOs/
│   └── DTO.php
└── Models/
    └── .php
```

Register all repository bindings in `app/Providers/RepositoryServiceProvider.php`.

### 2. Request Lifecycle

```
HTTP Request
  → FormRequest        (validate input)
  → Controller         (receive, delegate, respond — no logic)
  → Service            (business logic, orchestration)
  → Repository         (DB queries only)
  → Model              (Eloquent — no logic, only relationships + scopes)
  → API Resource       (format JSON response)
  → HTTP Response
```

### 3. Migration Conventions

Naming: `YYYY_MM_DD_HHMMSS_verb_noun_table.php`

```php
Schema::create('products', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('slug')->unique();
    $table->text('description')->nullable();
    $table->unsignedBigInteger('category_id');
    $table->decimal('price', 10, 2);          // money: always decimal(10,2)
    $table->unsignedInteger('stock')->default(0);
    $table->enum('status', ['active', 'inactive', 'draft'])->default('active');
    $table->json('meta')->nullable();
    $table->timestamps();
    $table->softDeletes();

    $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
    $table->index(['status', 'created_at']);
});
```

Column type rules:
- Money → `decimal(10, 2)`
- Status/type → `enum([...])` with default
- Images/files → `string` (path only)
- Flexible data → `json`
- Foreign key → `unsignedBigInteger` + `foreign()`
- Flags → `boolean()->default(false)`
- Pivot table name → alphabetical order (e.g. `category_product`)

### 4. Model

```php
class Product extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = ['name', 'slug', 'description', 'category_id', 'price', 'stock', 'status'];

    protected $casts = [
        'price'  => 'decimal:2',
        'stock'  => 'integer',
        'meta'   => 'array',
    ];

    // Relationships only — no business logic
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

    // Query scopes for common filters
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('status', 'active');
    }
}
```

### 5. DTO

```php
readonly class ProductDTO
{
    public function __construct(
        public string  $name,
        public string  $slug,
        public ?string $description,
        public int     $categoryId,
        public float   $price,
        public int     $stock,
        public string  $status = 'active',
    ) {}

    public static function fromRequest(array $data): self
    {
        return new self(
            name:        $data['name'],
            slug:        $data['slug'] ?? '',
            description: $data['description'] ?? null,
            categoryId:  $data['category_id'],
            price:       $data['price'],
            stock:       $data['stock'],
            status:      $data['status'] ?? 'active',
        );
    }
}
```

### 6. Repository

Interface in `app/Repositories/Interfaces/`:

```php
interface ProductRepositoryInterface
{
    public function paginate(array $filters, int $perPage = 20): LengthAwarePaginator;
    public function findById(int $id): ?Product;
    public function findBySlug(string $slug): ?Product;
    public function create(ProductDTO $dto): Product;
    public function update(Product $product, ProductDTO $dto): Product;
    public function delete(Product $product): bool;
}
```

Concrete in `app/Repositories/`:

```php
class ProductRepository implements ProductRepositoryInterface
{
    public function paginate(array $filters, int $perPage = 20): LengthAwarePaginator
    {
        return Product::query()
            ->with(['category', 'variants'])
            ->when(isset($filters['category_id']), fn($q) =>
                $q->where('category_id', $filters['category_id'])
            )
            ->when(isset($filters['search']), fn($q) =>
                $q->where('name', 'like', "%{$filters['search']}%")
            )
            ->latest()
            ->paginate($perPage);
    }

    public function create(ProductDTO $dto): Product
    {
        return Product::create([
            'name'        => $dto->name,
            'slug'        => $dto->slug,
            'category_id' => $dto->categoryId,
            'price'       => $dto->price,
            'stock'       => $dto->stock,
            'status'      => $dto->status,
        ]);
    }
    // findById, findBySlug, update, delete follow same pattern
}
```

Bind in `RepositoryServiceProvider`:

```php
$this->app->bind(ProductRepositoryInterface::class, ProductRepository::class);
```

### 7. Service

```php
class ProductService
{
    public function __construct(
        private readonly ProductRepositoryInterface $productRepository,
    ) {}

    public function createProduct(ProductDTO $dto): Product
    {
        // Business logic here — auto-generate slug, validate stock rules, fire events
        $dto = new ProductDTO(...$dto, slug: Str::slug($dto->name));
        return $this->productRepository->create($dto);
    }
}
```

### 8. Controller (thin)

```php
class ProductController extends Controller
{
    public function __construct(private readonly ProductService $productService) {}

    public function index(Request $request): ProductCollection
    {
        $products = $this->productService->getPaginatedProducts(
            filters: $request->only(['category_id', 'status', 'search']),
            perPage: $request->integer('per_page', 20),
        );
        return new ProductCollection($products);
    }

    public function store(StoreProductRequest $request): JsonResponse
    {
        $product = $this->productService->createProduct(
            ProductDTO::fromRequest($request->validated())
        );
        return (new ProductResource($product))->response()->setStatusCode(201);
    }
}
```

### 9. API Resource

```php
class ProductResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'         => $this->id,
            'name'       => $this->name,
            'slug'       => $this->slug,
            'price'      => (float) $this->price,
            'stock'      => $this->stock,
            'status'     => $this->status,
            'category'   => new CategoryResource($this->whenLoaded('category')),
            'created_at' => $this->created_at->toISOString(),
        ];
    }
}
```

Standard response shapes:
- Single: `{ "data": { ... } }`
- Collection: `{ "data": [...], "links": {...}, "meta": { "total": 100, ... } }`
- Error: `{ "message": "...", "errors": { "field": ["..."] } }`

### 10. Route Organization

```php
Route::prefix('v1')->group(function () {
    // Public
    Route::post('/auth/login', [AuthController::class, 'login']);
    Route::apiResource('products', ProductController::class)->only(['index', 'show']);

    // Authenticated
    Route::middleware('auth:sanctum')->group(function () {
        Route::post('/auth/logout', [AuthController::class, 'logout']);
        Route::apiResource('orders', OrderController::class);

        // Admin only
        Route::middleware('role:admin|super_admin')->prefix('admin')->group(function () {
            Route::apiResource('products', Admin\ProductController::class)->except(['index', 'show']);
        });
    });
});
```

## Examples

Prompt 1:
```text
Use laravel-architecture to create a new Category module with CRUD.
```

Prompt 2:
```text
Review this ProductController — does it follow the laravel-architecture conventions?
```

Prompt 3:
```text
Write the migration for an orders table using laravel-architecture conventions.
The table needs: user_id, total_amount, delivery_charge, status, paid_at, and delivery address fields.
```

## Smoke test

Ask the agent to scaffold a new `Order` module. Verify the response includes:
- Correct folder paths for Controller, Service, Repository, DTO, Resource
- Migration with proper column types (`decimal` for money, `enum` for status)
- Thin controller delegating to Service
- Repository with `paginate()` using `->when()` for filters
- DTO with `fromRequest()` factory
- Route group with auth middleware

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [ahmmedrasel-dev](https://github.com/ahmmedrasel-dev)
- **Source:** [ahmmedrasel-dev/agent-skills](https://github.com/ahmmedrasel-dev/agent-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-ahmmedrasel-dev-agent-skills-laravel-architecture
- Seller: https://agentstack.voostack.com/s/ahmmedrasel-dev
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
