技術 7 分鐘閱讀
Laravel API 設計最佳實踐:打造乾淨好維護的 API
前言
API 設計的好壞直接影響前後端協作效率和系統的可維護性。這篇文章整理了我在 Laravel 專案中常用的 API 設計模式和最佳實踐。
RESTful 路由設計
使用資源路由
Laravel 的資源路由已經幫你設計好標準的 CRUD 路由:
// routes/api.php
Route::apiResource('products', ProductController::class);
這會自動產生以下路由:
| 方法 | URI | 動作 |
|---|---|---|
| GET | /api/products | index |
| POST | /api/products | store |
| GET | /api/products/{id} | show |
| PUT | /api/products/{id} | update |
| DELETE | /api/products/{id} | destroy |
巢狀資源
Route::apiResource('products.reviews', ReviewController::class)
->scoped(['review' => 'id']);
產生的路由如 GET /api/products/{product}/reviews。
API 版本控制
Route::prefix('v1')->group(function () {
Route::apiResource('products', V1\ProductController::class);
});
Route::prefix('v2')->group(function () {
Route::apiResource('products', V2\ProductController::class);
});
統一回應格式
建立 API Response Trait
trait ApiResponse
{
protected function success($data, string $message = 'Success', int $code = 200)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data,
], $code);
}
protected function error(string $message, int $code = 400, $errors = null)
{
$response = [
'success' => false,
'message' => $message,
];
if ($errors) {
$response['errors'] = $errors;
}
return response()->json($response, $code);
}
}
在 Controller 中使用
class ProductController extends Controller
{
use ApiResponse;
public function index(Request $request)
{
$products = Product::with('category')
->paginate($request->get('per_page', 15));
return $this->success($products);
}
public function store(StoreProductRequest $request)
{
$product = Product::create($request->validated());
return $this->success($product, '商品建立成功', 201);
}
}
API Resource 轉換資料
不要直接回傳 Model,用 Resource 來控制輸出格式:
// app/Http/Resources/ProductResource.php
class ProductResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'price' => $this->price,
'formatted_price' => 'NT$ ' . number_format($this->price),
'category' => new CategoryResource($this->whenLoaded('category')),
'images' => ImageResource::collection($this->whenLoaded('images')),
'is_available' => $this->stock > 0,
'created_at' => $this->created_at->toISOString(),
];
}
}
在 Controller 中使用:
public function show(Product $product)
{
$product->load(['category', 'images', 'reviews']);
return new ProductResource($product);
}
public function index()
{
$products = Product::with('category')->paginate(15);
return ProductResource::collection($products);
}
表單驗證
使用 Form Request
// app/Http/Requests/StoreProductRequest.php
class StoreProductRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'price' => ['required', 'numeric', 'min:0'],
'category_id' => ['required', 'exists:categories,id'],
'description' => ['nullable', 'string'],
'images' => ['nullable', 'array'],
'images.*' => ['image', 'max:2048'],
];
}
public function messages(): array
{
return [
'name.required' => '商品名稱為必填',
'price.required' => '價格為必填',
'price.min' => '價格不能小於 0',
];
}
}
自動回傳驗證錯誤
Laravel 在 API 請求時會自動回傳 422 狀態碼和錯誤訊息,不需要手動處理。
錯誤處理
全域例外處理
在 app/Exceptions/Handler.php 中統一處理 API 錯誤:
public function render($request, Throwable $e)
{
if ($request->expectsJson()) {
if ($e instanceof ModelNotFoundException) {
return response()->json([
'success' => false,
'message' => '找不到指定的資源',
], 404);
}
if ($e instanceof AuthenticationException) {
return response()->json([
'success' => false,
'message' => '請先登入',
], 401);
}
if ($e instanceof AuthorizationException) {
return response()->json([
'success' => false,
'message' => '沒有權限執行此操作',
], 403);
}
}
return parent::render($request, $e);
}
篩選與排序
使用 Query Builder 的篩選模式
public function index(Request $request)
{
$query = Product::query();
// 篩選
if ($request->has('category_id')) {
$query->where('category_id', $request->category_id);
}
if ($request->has('min_price')) {
$query->where('price', '>=', $request->min_price);
}
if ($request->has('search')) {
$query->where('name', 'like', "%{$request->search}%");
}
// 排序
$sortField = $request->get('sort', 'created_at');
$sortDirection = $request->get('direction', 'desc');
$query->orderBy($sortField, $sortDirection);
return ProductResource::collection(
$query->with('category')->paginate($request->get('per_page', 15))
);
}
API 文件
使用 Scribe 自動產生文件
composer require knuckleswtf/scribe
php artisan vendor:publish --tag=scribe-config
php artisan scribe:generate
在 Controller 上加入註解:
/**
* 取得商品列表
*
* @queryParam category_id integer 分類 ID. Example: 1
* @queryParam search string 搜尋關鍵字. Example: iPhone
* @queryParam per_page integer 每頁筆數. Example: 15
*/
public function index(Request $request)
{
// ...
}
速率限制
// app/Providers/RouteServiceProvider.php
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by(
$request->user()?->id ?: $request->ip()
);
});
結語
好的 API 設計需要考慮一致性、可讀性和可擴展性。遵循 RESTful 原則、統一回應格式、做好錯誤處理,這些看似基本的事情做好了,前後端協作會順暢很多,系統也更容易維護。希望這篇文章對你有幫助。
#Laravel #API #RESTful #後端開發