Install
$ agentstack add skill-ahmmedrasel-dev-agent-skills-laravel-architecture ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
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
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
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
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/:
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/:
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:
$this->app->bind(ProductRepositoryInterface::class, ProductRepository::class);
7. Service
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)
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
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
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:
Use laravel-architecture to create a new Category module with CRUD.
Prompt 2:
Review this ProductController — does it follow the laravel-architecture conventions?
Prompt 3:
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 (
decimalfor money,enumfor 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
- Source: ahmmedrasel-dev/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.