laravel-api by juststeveking/laravel-api-skill
npx skills add https://github.com/juststeveking/laravel-api-skill --skill laravel-api使用简洁、无状态、资源作用域的架构构建 Laravel REST API。
当用户请求一个 Laravel API 时,请遵循以下工作流程:
阅读 references/architecture.md 获取完整细节。关键原则:
所有代码必须遵循 Laravel 最佳实践和 PSR-12 标准:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
在审查或重构代码时:
routes/api/
routes.php # 主入口点,版本分组
tasks.php # 所有任务路由,所有版本
projects.php # 所有项目路由,所有版本
app/Http/
Controllers/{Resource}/V1/
StoreController.php # 始终为可调用类
IndexController.php
ShowController.php
Requests/{Resource}/V1/
StoreTaskRequest.php # 验证 + payload() 方法
Payloads/{Resource}/
StoreTaskPayload.php # 带有 toArray() 的简单 DTO
Responses/
JsonDataResponse.php # 实现 Responsable 接口
JsonErrorResponse.php
Middleware/
HttpSunset.php
app/Actions/{Resource}/
CreateTask.php # 单一职责的业务逻辑
app/Services/ # 仅当逻辑过于复杂不适合放在 Actions 时使用
app/Models/
Task.php # HasUlids 特性,简单的数据访问
始终使用 ULID。保持模型简单 - 仅用于数据访问。
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class Task extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'title',
'description',
'status',
'project_id',
];
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
public function project(): BelongsTo
{
return $this->belongsTo(Project::class);
}
}
在 routes/api/{resource}.php 创建资源路由文件:
use App\Http\Controllers\Tasks\V1;
Route::middleware(['auth:api'])->group(function () {
Route::get('/tasks', V1\IndexController::class);
Route::post('/tasks', V1\StoreController::class);
Route::get('/tasks/{task}', V1\ShowController::class);
Route::patch('/tasks/{task}', V1\UpdateController::class);
Route::delete('/tasks/{task}', V1\DestroyController::class);
});
在 routes/api/routes.php 中包含:
Route::prefix('v1')->group(function () {
require __DIR__ . '/tasks.php';
});
在 app/Http/Payloads/{Resource}/{Operation}Payload.php 创建:
<?php
declare(strict_types=1);
namespace App\Http\Payloads\Tasks;
final readonly class StoreTaskPayload
{
public function __construct(
public string $title,
public ?string $description,
public string $status,
public string $projectId,
) {}
public function toArray(): array
{
return [
'title' => $this->title,
'description' => $this->description,
'status' => $this->status,
'project_id' => $this->projectId,
];
}
}
在 app/Http/Requests/{Resource}/V1/{Operation}Request.php 创建:
<?php
declare(strict_types=1);
namespace App\Http\Requests\Tasks\V1;
use App\Http\Payloads\Tasks\StoreTaskPayload;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
final class StoreTaskRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:1000'],
'status' => ['required', Rule::in(['pending', 'in_progress', 'completed'])],
'project_id' => ['required', 'string', 'exists:projects,id'],
];
}
public function payload(): StoreTaskPayload
{
return new StoreTaskPayload(
title: $this->string('title')->toString(),
description: $this->string('description')->toString(),
status: $this->string('status')->toString(),
projectId: $this->string('project_id')->toString(),
);
}
}
在 app/Actions/{Resource}/{Operation}.php 创建:
<?php
declare(strict_types=1);
namespace App\Actions\Tasks;
use App\Http\Payloads\Tasks\StoreTaskPayload;
use App\Models\Task;
final readonly class CreateTask
{
public function handle(StoreTaskPayload $payload): Task
{
return Task::create($payload->toArray());
}
}
在 app/Http/Controllers/{Resource}/V1/{Operation}Controller.php 创建可调用控制器:
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Tasks\V1;
use App\Actions\Tasks\CreateTask;
use App\Http\Requests\Tasks\V1\StoreTaskRequest;
use App\Http\Responses\JsonDataResponse;
use Illuminate\Http\JsonResponse;
final readonly class StoreController
{
public function __construct(
private CreateTask $createTask,
) {}
public function __invoke(StoreTaskRequest $request): JsonResponse
{
$task = $this->createTask->handle(
payload: $request->payload(),
);
return new JsonDataResponse(
data: $task,
status: 201,
);
}
}
所有响应的标准格式:
成功:
{
"data": {...},
"meta": {...}
}
错误 (Problem+JSON):
{
"type": "about:blank",
"title": "Validation Failed",
"status": 422,
"detail": "The given data was invalid",
"errors": {...}
}
使用 Spatie Query Builder 进行过滤、排序、包含关联:
use Spatie\QueryBuilder\QueryBuilder;
$tasks = QueryBuilder::for(Task::class)
->allowedFilters(['status', 'priority'])
->allowedSorts(['created_at', 'due_date'])
->allowedIncludes(['project', 'assignee'])
->paginate();
创建 V2 时:
App\Http\Controllers\Tasks\V2\Route::middleware(['auth:api', 'http.sunset:2025-12-31'])->group(function () {
// V1 路由
});
使用 PHP Open Source Saver JWT 包:
composer require php-open-source-saver/jwt-auth
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret
在 config/auth.php 中配置:
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
添加到 app/Providers/AppServiceProvider.php:
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::shouldBeStrict(); // 防止 N+1 查询
}
在 app/Http/Kernel.php 中注册 HttpSunset 中间件:
protected $middlewareAliases = [
'http.sunset' => \App\Http\Middleware\HttpSunset::class,
];
在审查或重构 Laravel API 代码时,应用以下原则:
用 match 替换嵌套三元运算符以提高清晰度:
// ❌ 避免:嵌套三元运算符
$status = $task->completed_at
? ($task->verified ? 'verified' : 'completed')
: ($task->started_at ? 'in_progress' : 'pending');
// ✅ 推荐:Match 表达式
$status = match (true) {
$task->completed_at && $task->verified => 'verified',
$task->completed_at => 'completed',
$task->started_at => 'in_progress',
default => 'pending',
};
assets/templates/ 中的模板文件,用于快速搭建:
每周安装次数
78
代码仓库
GitHub 星标数
17
首次出现
2026年1月24日
安全审计
安装于
opencode70
codex68
gemini-cli67
github-copilot65
amp60
kimi-cli60
Build Laravel REST APIs with clean, stateless, resource-scoped architecture.
When user requests a Laravel API, follow this workflow:
Read references/architecture.md for comprehensive details. Key principles:
All code must follow Laravel best practices and PSR-12 standards:
When reviewing or refactoring code:
routes/api/
routes.php # Main entry point, version grouping
tasks.php # All task routes, all versions
projects.php # All project routes, all versions
app/Http/
Controllers/{Resource}/V1/
StoreController.php # Always invokable
IndexController.php
ShowController.php
Requests/{Resource}/V1/
StoreTaskRequest.php # Validation + payload() method
Payloads/{Resource}/
StoreTaskPayload.php # Simple DTOs with toArray()
Responses/
JsonDataResponse.php # Implements Responsable
JsonErrorResponse.php
Middleware/
HttpSunset.php
app/Actions/{Resource}/
CreateTask.php # Single-purpose business logic
app/Services/ # Only when logic too complex for Actions
app/Models/
Task.php # HasUlids trait, simple data access
Always use ULIDs. Keep models simple - data access only.
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class Task extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'title',
'description',
'status',
'project_id',
];
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
public function project(): BelongsTo
{
return $this->belongsTo(Project::class);
}
}
Create resource route file at routes/api/{resource}.php:
use App\Http\Controllers\Tasks\V1;
Route::middleware(['auth:api'])->group(function () {
Route::get('/tasks', V1\IndexController::class);
Route::post('/tasks', V1\StoreController::class);
Route::get('/tasks/{task}', V1\ShowController::class);
Route::patch('/tasks/{task}', V1\UpdateController::class);
Route::delete('/tasks/{task}', V1\DestroyController::class);
});
Include in routes/api/routes.php:
Route::prefix('v1')->group(function () {
require __DIR__ . '/tasks.php';
});
Create at app/Http/Payloads/{Resource}/{Operation}Payload.php:
<?php
declare(strict_types=1);
namespace App\Http\Payloads\Tasks;
final readonly class StoreTaskPayload
{
public function __construct(
public string $title,
public ?string $description,
public string $status,
public string $projectId,
) {}
public function toArray(): array
{
return [
'title' => $this->title,
'description' => $this->description,
'status' => $this->status,
'project_id' => $this->projectId,
];
}
}
Create at app/Http/Requests/{Resource}/V1/{Operation}Request.php:
<?php
declare(strict_types=1);
namespace App\Http\Requests\Tasks\V1;
use App\Http\Payloads\Tasks\StoreTaskPayload;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
final class StoreTaskRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:1000'],
'status' => ['required', Rule::in(['pending', 'in_progress', 'completed'])],
'project_id' => ['required', 'string', 'exists:projects,id'],
];
}
public function payload(): StoreTaskPayload
{
return new StoreTaskPayload(
title: $this->string('title')->toString(),
description: $this->string('description')->toString(),
status: $this->string('status')->toString(),
projectId: $this->string('project_id')->toString(),
);
}
}
Create at app/Actions/{Resource}/{Operation}.php:
<?php
declare(strict_types=1);
namespace App\Actions\Tasks;
use App\Http\Payloads\Tasks\StoreTaskPayload;
use App\Models\Task;
final readonly class CreateTask
{
public function handle(StoreTaskPayload $payload): Task
{
return Task::create($payload->toArray());
}
}
Create invokable controller at app/Http/Controllers/{Resource}/V1/{Operation}Controller.php:
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Tasks\V1;
use App\Actions\Tasks\CreateTask;
use App\Http\Requests\Tasks\V1\StoreTaskRequest;
use App\Http\Responses\JsonDataResponse;
use Illuminate\Http\JsonResponse;
final readonly class StoreController
{
public function __construct(
private CreateTask $createTask,
) {}
public function __invoke(StoreTaskRequest $request): JsonResponse
{
$task = $this->createTask->handle(
payload: $request->payload(),
);
return new JsonDataResponse(
data: $task,
status: 201,
);
}
}
Standard format for all responses:
Success:
{
"data": {...},
"meta": {...}
}
Error (Problem+JSON):
{
"type": "about:blank",
"title": "Validation Failed",
"status": 422,
"detail": "The given data was invalid",
"errors": {...}
}
Use Spatie Query Builder for filtering, sorting, includes:
use Spatie\QueryBuilder\QueryBuilder;
$tasks = QueryBuilder::for(Task::class)
->allowedFilters(['status', 'priority'])
->allowedSorts(['created_at', 'due_date'])
->allowedIncludes(['project', 'assignee'])
->paginate();
When creating V2:
App\Http\Controllers\Tasks\V2\Route::middleware(['auth:api', 'http.sunset:2025-12-31'])->group(function () {
// V1 routes
});
Use PHP Open Source Saver JWT package:
composer require php-open-source-saver/jwt-auth
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret
Configure in config/auth.php:
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
Add to app/Providers/AppServiceProvider.php:
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::shouldBeStrict(); // Prevent N+1 queries
}
Register HttpSunset middleware in app/Http/Kernel.php:
protected $middlewareAliases = [
'http.sunset' => \App\Http\Middleware\HttpSunset::class,
];
When reviewing or refactoring Laravel API code, apply these principles:
Replace nested ternaries with match for clarity:
// ❌ Avoid: Nested ternary
$status = $task->completed_at
? ($task->verified ? 'verified' : 'completed')
: ($task->started_at ? 'in_progress' : 'pending');
// ✅ Prefer: Match expression
$status = match (true) {
$task->completed_at && $task->verified => 'verified',
$task->completed_at => 'completed',
$task->started_at => 'in_progress',
default => 'pending',
};
Template files in assets/templates/ for quick scaffolding:
Weekly Installs
78
Repository
GitHub Stars
17
First Seen
Jan 24, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode70
codex68
gemini-cli67
github-copilot65
amp60
kimi-cli60
lark-cli 共享规则:飞书资源操作指南与权限配置详解
39,000 周安装