A REST API is the main foundation in modern application development, both for mobile apps and separate frontends (SPAs). Laravel provides very complete tools for building clean and structured REST APIs. In this tutorial, you'll learn to build a complete CRUD (Create, Read, Update, Delete) REST API using Laravel 10/11.
Preparing the Laravel Project
Make sure you already have a Laravel project. If not, create one with the following command:
composer create-project laravel/laravel laravel-api
cd laravel-api
php artisan serve
Create the model, migration, and resource controller all at once with a single command:
php artisan make:model Product -mcr
The -m flag creates the migration, -c creates the controller, and -r makes it a resource controller.
Creating the Migration and Model
Open the newly created migration file in database/migrations/ and add the columns you need:
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 10, 2);
$table->integer('stock')->default(0);
$table->timestamps();
});
}
Then edit the model app/Models/Product.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = [
'name',
'description',
'price',
'stock',
];
}
Run the migration:
php artisan migrate
Defining the API Routes
Open the routes/api.php file and add the resource route:
<?php
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::apiResource('products', ProductController::class);
The apiResource command automatically creates 5 routes: index, store, show, update, and destroy. You can check them with:
php artisan route:list --path=api
Creating an API Resource for the Response
Use a Laravel API Resource so the JSON response format is consistent:
php artisan make:resource ProductResource
Edit 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' => (float) $this->price,
'stock' => $this->stock,
'created_at' => $this->created_at->toDateTimeString(),
];
}
}
Writing the Controller Logic
Open app/Http/Controllers/ProductController.php and fill in each method:
<?php
namespace App\Http\Controllers;
use App\Http\Resources\ProductResource;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class ProductController extends Controller
{
public function index(): JsonResponse
{
$products = Product::latest()->paginate(10);
return ProductResource::collection($products)->response();
}
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'price' => 'required|numeric|min:0',
'stock' => 'required|integer|min:0',
]);
$product = Product::create($validated);
return (new ProductResource($product))
->response()
->setStatusCode(201);
}
public function show(Product $product): JsonResponse
{
return (new ProductResource($product))->response();
}
public function update(Request $request, Product $product): JsonResponse
{
$validated = $request->validate([
'name' => 'sometimes|string|max:255',
'description' => 'nullable|string',
'price' => 'sometimes|numeric|min:0',
'stock' => 'sometimes|integer|min:0',
]);
$product->update($validated);
return (new ProductResource($product))->response();
}
public function destroy(Product $product): JsonResponse
{
$product->delete();
return response()->json([
'message' => 'Product deleted successfully.',
]);
}
}
Testing the API with Postman or cURL
Example requests using cURL:
# GET all products
curl -X GET http://localhost:8000/api/products \
-H "Accept: application/json"
# POST a new product
curl -X POST http://localhost:8000/api/products \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name":"Laptop","price":12000000,"stock":5}'
# PUT update a product
curl -X PUT http://localhost:8000/api/products/1 \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"price":11500000}'
# DELETE a product
curl -X DELETE http://localhost:8000/api/products/1 \
-H "Accept: application/json"
Handling Errors with a Consistent Response
For neat error handling, you can add a response helper in the controller or create a dedicated trait:
// Success response
return response()->json([
'success' => true,
'data' => new ProductResource($product),
'message' => 'Product created successfully.',
], 201);
// Validation failure response (automatic by Laravel)
// Status 422 Unprocessable Entity
Conclusion
You've successfully built a complete CRUD REST API in Laravel using a Resource Controller, an API Resource for JSON formatting, and Laravel's built-in validation. The next step is to add authentication using Laravel Sanctum so your API endpoints are protected. With this structure, your API is ready to be consumed by a React or Vue frontend or a mobile app.