1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravel11でAPIの実装する

Last updated at Posted at 2024-10-29

概要

この記事ではLaravel11を利用して基本的なAPIを実装手順を紹介します。

実装する機能

  • 商品一覧の取得(検索、ソート、ページネーション)
  • 商品の登録
  • 商品の詳細取得
  • 商品の更新
  • 商品の削除(論理削除、物理削除、復元)

実装手順

1. Laravel11のプロジェクトの作成

composer create-project laravel/laravel=11.x product-management-api
cd product-management-api

2. モデルとマイグレーションの作成

まず、Product モデルとマイグレーションファイルを作成します。

php artisan make:model Product -m

マイグレーションファイル(database/migrations/xxxx_xx_xx_create_products_table.php):

public function up(): void
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->text('description')->nullable();
        $table->integer('price');
        $table->integer('stock');
        $table->boolean('is_active')->default(true);
        $table->timestamps();
        $table->softDeletes();
    });
}

Product モデル(app/Models/Product.php):

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Product extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = [
        'name',
        'description',
        'price',
        'stock',
        'is_active'
    ];

    protected $casts = [
        'price' => 'integer',
        'stock' => 'integer',
        'is_active' => 'boolean',
    ];
}

3. API リソースの作成

php artisan make:resource ProductResource

app/Http/Resources/ProductResource.php:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class ProductResource extends JsonResource
{

    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'description' => $this->description,
            'price' => $this->price,
            'stock' => $this->stock,
            'is_active' => $this->is_active,
            'created_at' => $this->created_at->format('Y-m-d H:i:s'),
            'updated_at' => $this->updated_at->format('Y-m-d H:i:s'),
        ];
    }
}

4. バリデーションの作成

商品登録用のリクエストクラス:

php artisan make:request Api/CreateProductRequest
<?php

namespace App\Http\Requests\Api;

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Symfony\Component\HttpFoundation\Response;

class CreateProductRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'description' => ['nullable', 'string', 'max:1000'],
            'price' => ['required', 'integer', 'min:0'],
            'stock' => ['required', 'integer', 'min:0'],
            'is_active' => ['boolean'],
        ];
    }

    public function messages(): array
    {
        return [
            'name.required' => '商品名は必須です',
            'name.max' => '商品名は255文字以内で入力してください',
            'description.max' => '商品説明は1000文字以内で入力してください',
            'price.required' => '価格は必須です',
            'price.integer' => '価格は整数で入力してください',
            'price.min' => '価格は0以上で入力してください',
            'stock.required' => '在庫数は必須です',
            'stock.integer' => '在庫数は整数で入力してください',
            'stock.min' => '在庫数は0以上で入力してください',
            'is_active.boolean' => '商品状態は真偽値で入力してください',
        ];
    }

    protected function failedValidation(Validator $validator)
    {
        throw new HttpResponseException(
            response()->json([
                'message' => 'Validation failed',
                'errors' => $validator->errors(),
            ], Response::HTTP_UNPROCESSABLE_ENTITY)
        );
    }
}

商品更新用のリクエストクラス:

php artisan make:request Api/UpdateProductRequest
<?php

namespace App\Http\Requests\Api;

class UpdateProductRequest extends CreateProductRequest
{
    public function rules(): array
    {
        $rules = parent::rules();
        
        // 更新時は送信されたフィールドのみをバリデーション
        return array_map(function ($rule) {
            return array_merge(['sometimes'], $rule);
        }, $rules);
    }
}

5. コントローラーの作成

php artisan make:controller Api/ProductController

app/Http/Controllers/Api/ProductController.php:

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\Api\CreateProductRequest;
use App\Http\Requests\Api\UpdateProductRequest;
use App\Http\Resources\ProductResource;
use App\Models\Product;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Symfony\Component\HttpFoundation\Response;

class ProductController extends Controller
{
    /**
     * 許可されているソートカラムのリスト
     */
    private const ALLOWED_SORT_COLUMNS = [
        'id',
        'name',
        'price',
        'stock',
        'created_at',
        'updated_at'
    ];

    /**
     * 商品一覧を取得
     */
    public function index(Request $request): AnonymousResourceCollection
    {
        try {
            $query = Product::query();

            // 検索条件の追加
            if ($request->has('name')) {
                $query->where('name', 'like', '%' . $request->input('name') . '%');
            }

            if ($request->has('is_active')) {
                $query->where('is_active', $request->boolean('is_active'));
            }

            // ソート条件の追加
            $sortBy = $request->input('sort_by', 'created_at');
            $sortOrder = $request->input('sort_order', 'desc');

            // 不正なソートカラムのチェック
            if (!in_array($sortBy, self::ALLOWED_SORT_COLUMNS, true)) {
                throw new \InvalidArgumentException("Invalid sort column: {$sortBy}");
            }

            $query->orderBy($sortBy, $sortOrder);

            // ページネーション
            $products = $query->paginate($request->input('per_page', 15));

            return ProductResource::collection($products);
        } catch (\InvalidArgumentException $e) {
            abort(Response::HTTP_BAD_REQUEST, $e->getMessage());
        } catch (\Exception $e) {
            abort(Response::HTTP_INTERNAL_SERVER_ERROR, 'Failed to fetch products.');
        }
    }

    /**
     * 商品を作成
     */
    public function store(CreateProductRequest $request): JsonResponse
    {
        try {
            $product = Product::create($request->validated());

            return response()->json([
                'message' => 'Product created successfully',
                'data' => new ProductResource($product)
            ], Response::HTTP_CREATED);
        } catch (\Exception $e) {
            return response()->json([
                'message' => 'Failed to create product',
                'error' => $e->getMessage()
            ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 商品を更新
     */
    public function update(UpdateProductRequest $request, int $id): JsonResponse
    {
        try {
            $product = Product::findOrFail($id);

            $product->update($request->validated());

            return response()->json([
                'message' => 'Product updated successfully',
                'data' => new ProductResource($product)
            ], Response::HTTP_OK);
        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
            return response()->json([
                'message' => 'Product not found'
            ], Response::HTTP_NOT_FOUND);
        } catch (\Exception $e) {
            return response()->json([
                'message' => 'Failed to update product',
                'error' => $e->getMessage()
            ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 商品を取得
     */
    public function show(int $id): JsonResponse
    {
        try {
            $product = Product::findOrFail($id);

            return response()->json([
                'data' => new ProductResource($product)
            ], Response::HTTP_OK);
        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
            return response()->json([
                'message' => 'Product not found'
            ], Response::HTTP_NOT_FOUND);
        } catch (\Exception $e) {
            return response()->json([
                'message' => 'Failed to fetch product',
                'error' => $e->getMessage()
            ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 商品を削除
     */
    public function destroy(int $id): JsonResponse
    {
        try {
            // withTrashedを追加して、削除済みのレコードも検索対象に含める
            $product = Product::withTrashed()->findOrFail($id);

            if ($product->trashed()) {
                return response()->json([
                    'message' => 'Product is already deleted'
                ], Response::HTTP_BAD_REQUEST);
            }

            $product->delete();

            return response()->json([
                'message' => 'Product deleted successfully'
            ], Response::HTTP_OK);
        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
            return response()->json([
                'message' => 'Product not found'
            ], Response::HTTP_NOT_FOUND);
        } catch (\Exception $e) {
            return response()->json([
                'message' => 'Failed to delete product',
                'error' => $e->getMessage()
            ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 商品を強制的に削除
     */
    public function forceDestroy(int $id): JsonResponse
    {
        try {
            $product = Product::withTrashed()->findOrFail($id);

            $product->forceDelete();

            return response()->json([
                'message' => 'Product permanently deleted successfully'
            ], Response::HTTP_OK);
        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
            return response()->json([
                'message' => 'Product not found'
            ], Response::HTTP_NOT_FOUND);
        } catch (\Exception $e) {
            return response()->json([
                'message' => 'Failed to permanently delete product',
                'error' => $e->getMessage()
            ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 削除済み商品の復元
     */
    public function restore(int $id): JsonResponse
    {
        try {
            $product = Product::withTrashed()->findOrFail($id);

            if (!$product->trashed()) {
                return response()->json([
                    'message' => 'Product is not deleted'
                ], Response::HTTP_BAD_REQUEST);
            }

            $product->restore();

            return response()->json([
                'message' => 'Product restored successfully',
                'data' => new ProductResource($product)
            ], Response::HTTP_OK);
        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
            return response()->json([
                'message' => 'Product not found'
            ], Response::HTTP_NOT_FOUND);
        } catch (\Exception $e) {
            return response()->json([
                'message' => 'Failed to restore product',
                'error' => $e->getMessage()
            ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }
}

5. ルーティングの設定

Laravel11のデフォルトではroutes/api.phpが存在しないため以下のコマンドで生成してください。

php artisan install:api

routes/api.php:

<?php

use App\Http\Controllers\Api\ProductController;

Route::get('/products', [ProductController::class, 'index']);
Route::post('/products', [ProductController::class, 'store']);
Route::get('/products/{id}', [ProductController::class, 'show']);
Route::put('/products/{id}', [ProductController::class, 'update']);
Route::delete('/products/{id}', [ProductController::class, 'destroy']);
Route::delete('/products/{id}/force', [ProductController::class, 'forceDestroy']);
Route::patch('/products/{id}/restore', [ProductController::class, 'restore']);

6. テストの作成

php artisan make:test Api/ProductTest

tests/Feature/Api/ProductTest.php:

<?php

namespace Tests\Feature\Api;

use App\Models\Product;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Symfony\Component\HttpFoundation\Response;
use Tests\TestCase;

/**
 * 商品APIのテスト
 * 
 */
class ProductTest extends TestCase
{
    use RefreshDatabase;

    /**
     * テストデータの作成
     */
    public function setUp(): void
    {
        parent::setUp();
        // テストデータの作成
        Product::factory(30)->create();
    }

    /**
     * 商品一覧を取得できるかのテスト
     */
    public function test_can_fetch_products_list(): void
    {
        $response = $this->getJson('/api/products');

        $response->assertStatus(200)
            ->assertJsonStructure([
                'data' => [
                    '*' => [
                        'id',
                        'name',
                        'description',
                        'price',
                        'stock',
                        'is_active',
                        'created_at',
                        'updated_at',
                    ],
                ],
                'links',
                'meta',
            ]);
    }

    /**
     * 商品名で検索できるかのテスト
     */
    public function test_can_fetch_products_with_search_params(): void
    {
        // 特定の名前を持つ商品を作成
        $searchName = 'テスト商品';
        Product::factory()->create(['name' => $searchName]);

        $response = $this->getJson("/api/products?name={$searchName}");

        $response->assertStatus(200)
            ->assertJsonFragment(['name' => $searchName]);
    }

    /**
     * アクティブな商品のみ取得できるかのテスト
     */
    public function test_can_fetch_active_products_only(): void
    {
        // アクティブな商品を作成
        Product::factory(5)->create(['is_active' => true]);
        // 非アクティブな商品を作成
        Product::factory(5)->create(['is_active' => false]);

        $response = $this->getJson('/api/products?is_active=true');

        $response->assertStatus(200);
        $products = $response->json('data');
        collect($products)->each(function ($product) {
            $this->assertTrue($product['is_active']);
        });
    }

    /**
     * 商品を価格でソートできるかのテスト
     */
    public function test_can_sort_products(): void
    {
        // 価格の異なる商品を作成
        Product::factory()->create(['price' => 1000]);
        Product::factory()->create(['price' => 2000]);
        Product::factory()->create(['price' => 3000]);

        $response = $this->getJson('/api/products?sort_by=price&sort_order=desc');

        $response->assertStatus(200);
        $products = $response->json('data');
        $this->assertTrue($products[0]['price'] > $products[1]['price']);
    }

    /**
     * ページネーションができるかのテスト
     */
    public function test_can_paginate_products(): void
    {
        $perPage = 5;
        $response = $this->getJson("/api/products?per_page={$perPage}");

        $response->assertStatus(200)
            ->assertJsonCount($perPage, 'data')
            ->assertJsonStructure([
                'meta' => [
                    'current_page',
                    'from',
                    'last_page',
                    'per_page',
                    'to',
                    'total',
                ],
            ]);
    }

    /**
     * 無効なソートパラメータのテスト
     */
    public function test_handles_invalid_sort_parameter(): void
    {
        $response = $this->getJson('/api/products?sort_by=invalid_column');

        $response->assertStatus(Response::HTTP_BAD_REQUEST)
            ->assertJsonFragment([
                'message' => 'Invalid sort column: invalid_column'
            ]);
    }

    /**
     * 商品を作成できるかのテスト
     */
    public function test_can_create_product(): void
    {
        $productData = [
            'name' => 'テスト商品',
            'description' => '商品の説明文です',
            'price' => 1000,
            'stock' => 10,
            'is_active' => true,
        ];

        $response = $this->postJson('/api/products', $productData);

        $response->assertStatus(Response::HTTP_CREATED)
            ->assertJsonStructure([
                'message',
                'data' => [
                    'id',
                    'name',
                    'description',
                    'price',
                    'stock',
                    'is_active',
                    'created_at',
                    'updated_at',
                ]
            ]);

        $this->assertDatabaseHas('products', $productData);
    }

    /**
     * 無効なデータで商品を作成できないかのテスト
     */
    public function test_cannot_create_product_with_invalid_data(): void
    {
        $invalidData = [
            'name' => '', // 必須項目を空にする
            'price' => -1, // 負の値は不許可
            'stock' => 'invalid', // 数値以外は不許可
        ];

        $response = $this->postJson('/api/products', $invalidData);

        $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
            ->assertJsonStructure([
                'message',
                'errors' => [
                    'name',
                    'price',
                    'stock',
                ]
            ]);
    }

    /**
     * 商品を更新できるかのテスト
     */
    public function test_can_update_product(): void
    {
        $product = Product::factory()->create();

        $updateData = [
            'name' => '更新後の商品名',
            'price' => 2000,
        ];

        $response = $this->putJson("/api/products/{$product->id}", $updateData);

        $response->assertStatus(Response::HTTP_OK)
            ->assertJsonStructure([
                'message',
                'data' => [
                    'id',
                    'name',
                    'description',
                    'price',
                    'stock',
                    'is_active',
                    'created_at',
                    'updated_at',
                ]
            ])
            ->assertJsonFragment([
                'name' => '更新後の商品名',
                'price' => 2000,
            ]);

        $this->assertDatabaseHas('products', $updateData);
    }

    /**
     * 無効なデータで商品を更新できないかのテスト
     */
    public function test_cannot_update_product_with_invalid_data(): void
    {
        $product = Product::factory()->create();

        $invalidData = [
            'name' => '', // 空の名前は不許可
            'price' => -1, // 負の価格は不許可
        ];

        $response = $this->putJson("/api/products/{$product->id}", $invalidData);

        $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
            ->assertJsonStructure([
                'message',
                'errors' => [
                    'name',
                    'price',
                ]
            ]);
    }

    /**
     * 存在しない商品を更新しようとした場合のテスト
     */
    public function test_cannot_update_non_existent_product(): void
    {
        $nonExistentId = 9999;

        $updateData = [
            'name' => '更新後の商品名',
            'price' => 2000,
        ];

        $response = $this->putJson("/api/products/{$nonExistentId}", $updateData);

        $response->assertStatus(Response::HTTP_NOT_FOUND)
            ->assertJsonFragment([
                'message' => 'Product not found'
            ]);
    }

    /**
     * 商品を取得できるかのテスト
     */
    public function test_can_fetch_single_product(): void
    {
        $product = Product::factory()->create([
            'name' => 'テスト商品',
            'description' => '商品の説明文',
            'price' => 1000,
            'stock' => 10,
            'is_active' => true,
        ]);

        $response = $this->getJson("/api/products/{$product->id}");

        $response->assertStatus(Response::HTTP_OK)
            ->assertJsonStructure([
                'data' => [
                    'id',
                    'name',
                    'description',
                    'price',
                    'stock',
                    'is_active',
                    'created_at',
                    'updated_at',
                ]
            ])
            ->assertJsonFragment([
                'name' => 'テスト商品',
                'description' => '商品の説明文',
                'price' => 1000,
                'stock' => 10,
                'is_active' => true,
            ]);
    }

    /**
     * 存在しない商品を取得しようとした場合のテスト
     */
    public function test_cannot_fetch_non_existent_product(): void
    {
        $nonExistentId = 9999;

        $response = $this->getJson("/api/products/{$nonExistentId}");

        $response->assertStatus(Response::HTTP_NOT_FOUND)
            ->assertJsonFragment([
                'message' => 'Product not found'
            ]);
    }

    /**
     * 削除された商品を取得しようとした場合のテスト
     */
    public function test_cannot_fetch_deleted_product(): void
    {
        $product = Product::factory()->create();
        $product->delete();

        $response = $this->getJson("/api/products/{$product->id}");

        $response->assertStatus(Response::HTTP_NOT_FOUND)
            ->assertJsonFragment([
                'message' => 'Product not found'
            ]);
    }

    /**
     * 無効なID形式で商品を取得しようとした場合のテスト
     */
    public function test_returns_error_for_invalid_id_format(): void
    {
        $response = $this->getJson('/products/invalid-id');

        $response->assertStatus(Response::HTTP_NOT_FOUND);
    }

    /**
     * 商品を論理削除できるかのテスト
     */
    public function test_can_soft_delete_product(): void
    {
        $product = Product::factory()->create();

        $response = $this->deleteJson("/api/products/{$product->id}");

        $response->assertStatus(Response::HTTP_OK)
            ->assertJsonFragment([
                'message' => 'Product deleted successfully'
            ]);

        $this->assertSoftDeleted('products', [
            'id' => $product->id
        ]);
    }

    /**
     * 存在しない商品を削除しようとした場合のテスト
     */
    public function test_cannot_delete_non_existent_product(): void
    {
        $nonExistentId = 9999;

        $response = $this->deleteJson("/api/products/{$nonExistentId}");

        $response->assertStatus(Response::HTTP_NOT_FOUND)
            ->assertJsonFragment([
                'message' => 'Product not found'
            ]);
    }

    /**
     * 削除済みの商品を削除しようとした場合のテスト
     */
    public function test_cannot_delete_already_deleted_product(): void
    {
        $product = Product::factory()->create();
        $product->delete();

        $response = $this->deleteJson("/api/products/{$product->id}");

        $response->assertStatus(Response::HTTP_BAD_REQUEST)
            ->assertJsonFragment([
                'message' => 'Product is already deleted'
            ]);
    }

    /**
     * 商品を強制的に削除できるかのテスト
     */
    public function test_can_force_delete_product(): void
    {
        $product = Product::factory()->create();
        $product->delete(); // まずソフトデリート

        $response = $this->deleteJson("/api/products/{$product->id}/force");

        $response->assertStatus(Response::HTTP_OK)
            ->assertJsonFragment([
                'message' => 'Product permanently deleted successfully'
            ]);

        $this->assertDatabaseMissing('products', [
            'id' => $product->id
        ]);
    }

    /**
     * 削除済みの商品を復元できるかのテスト
     */
    public function test_can_restore_deleted_product(): void
    {
        $product = Product::factory()->create();
        $product->delete();

        $response = $this->patchJson("/api/products/{$product->id}/restore");  // postJsonからpatchJsonに変更

        $response->assertStatus(Response::HTTP_OK)
            ->assertJsonFragment([
                'message' => 'Product restored successfully'
            ]);

        $this->assertDatabaseHas('products', [
            'id' => $product->id,
            'deleted_at' => null
        ]);
    }

    /**
     * 削除済みでない商品を復元しようとした場合のテスト
     */
    public function test_cannot_restore_non_deleted_product(): void
    {
        $product = Product::factory()->create();

        $response = $this->patchJson("/api/products/{$product->id}/restore");  // postJsonからpatchJsonに変更

        $response->assertStatus(Response::HTTP_BAD_REQUEST)
            ->assertJsonFragment([
                'message' => 'Product is not deleted'
            ]);
    }
}

.envの内容をコピーして、.env.testingを作成する

 cp .env .env.testing

.env.testingに以下を追加:

DB_CONNECTION=sqlite
DB_DATABASE=:memory:

テスト実行:


php artisan test tests/Feature/Api/ProductTest.php

   PASS  Tests\Feature\Api\ProductTest
  ✓ can fetch products list                                                                                                                    0.14s  
  ✓ can fetch products with search params                                                                                                      0.04s  
  ✓ can fetch active products only                                                                                                             0.04s  
  ✓ can sort products                                                                                                                          0.03s  
  ✓ can paginate products                                                                                                                      0.04s  
  ✓ handles invalid sort parameter                                                                                                             0.03s  
  ✓ can create product                                                                                                                         0.04s  
  ✓ cannot create product with invalid data                                                                                                    0.04s  
  ✓ can update product                                                                                                                         0.03s  
  ✓ cannot update product with invalid data                                                                                                    0.03s  
  ✓ cannot update non existent product                                                                                                         0.04s  
  ✓ can fetch single product                                                                                                                   0.03s  
  ✓ cannot fetch non existent product                                                                                                          0.03s  
  ✓ cannot fetch deleted product                                                                                                               0.04s  
  ✓ returns error for invalid id format                                                                                                        0.03s  
  ✓ can soft delete product                                                                                                                    0.03s  
  ✓ cannot delete non existent product                                                                                                         0.04s  
  ✓ cannot delete already deleted product                                                                                                      0.03s  
  ✓ can force delete product                                                                                                                   0.03s  
  ✓ can restore deleted product                                                                                                                0.04s  
  ✓ cannot restore non deleted product                                                                                                         0.03s  

  Tests:    21 passed (230 assertions)
  Duration: 0.90s

7. ダミーデータの作成(オプション)

まず、ProductFactoryを作成します:

php artisan make:factory ProductFactory

database/factories/ProductFactory.php

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class ProductFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->realText(20),
            'description' => fake()->realText(200),
            'price' => fake()->numberBetween(100, 100000),
            'stock' => fake()->numberBetween(0, 100),
            'is_active' => fake()->boolean(80), // 80%の確率でtrue
            'created_at' => fake()->dateTimeBetween('-1 year', 'now'),
            'updated_at' => function (array $attributes) {
                return fake()->dateTimeBetween($attributes['created_at'], 'now');
            },
        ];
    }
}

次に、シーダーを作成します:

php artisan make:seeder ProductSeeder

database/seeders/ProductSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Product;
use Illuminate\Database\Seeder;

class ProductSeeder extends Seeder
{
    public function run(): void
    {
        Product::factory(50)->create();
    }
}

database/seeders/DatabaseSeeder.php

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            ProductSeeder::class,
        ]);
    }
}

テストデータを投入します:

php artisan db:seed

API エンドポイント一覧

メソッド エンドポイント 説明
GET /api/products 商品一覧の取得
POST /api/products 商品の登録
GET /api/products/{id} 商品詳細の取得
PUT /api/products/{id} 商品の更新
DELETE /api/products/{id} 商品の論理削除
DELETE /api/products/{id}/force 商品の物理削除
PATCH /api/products/{id}/restore 削除商品の復元

さいごに

この記事ではLaravel 11を使用して基本的な商品管理APIを実装しました。実装のポイントを振り返ってみます。

  1. 基本に忠実な実装
  • Laravel の機能を活用したCRUD操作
  • RESTful APIの原則に従った設計
  • リクエストクラスによるバリデーション
  1. 実践的な機能
  • 検索、ソート、ページネーション
  • 論理削除と復元
  • エラーハンドリング
  1. テストの重要性
  • 各機能の動作確認
  • エッジケースのテスト
  • テストコードによる仕様明確化

学んだ内容を土台として、認証機能の実装やキャッシュによるパフォーマンス改善、API仕様書の作成など、さらなる学習を進めていければと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?