Kritim Yantra
Mar 23, 2025
In this blog, we'll walk through creating a simple CRUD (Create, Read, Update, Delete) API using Laravel 12. We'll also implement proper error handling to ensure that our API is robust and user-friendly. By the end of this tutorial, you'll have a fully functional API with error handling in place.
First, let's create a new Laravel project. Open your terminal and run the following command:
composer create-project laravel/laravel laravel-crud-api
Once the project is created, navigate into the project directory:
cd laravel-crud-api
Next, we need to configure our database. Open the .env
file and update the database credentials:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_crud_api
DB_USERNAME=root
DB_PASSWORD=
Now, run the migrations to create the necessary tables:
php artisan migrate
Install API:
php artisan install:api
Let's create a model and migration for our Product
entity. Run the following command:
php artisan make:model Product -m
This will create a Product
model and a migration file. Open the migration file located in database/migrations
and define the schema:
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description');
$table->decimal('price', 8, 2);
$table->timestamps();
});
}
Run the migration to create the products
table:
php artisan migrate
Now, let's create a controller to handle our CRUD operations. Run the following command:
php artisan make:controller ProductController --api
This will generate a controller with the basic CRUD methods. Open the ProductController
located in app/Http/Controllers
and implement the methods:
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
// Get all products
public function index()
{
$products = Product::all();
return response()->json($products);
}
// Get a single product
public function show($id)
{
$product = Product::find($id);
if (!$product) {
return response()->json(['message' => 'Product not found'], 404);
}
return response()->json($product);
}
// Create a new product
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'description' => 'required|string',
'price' => 'required|numeric',
]);
$product = Product::create($request->all());
return response()->json($product, 201);
}
// Update a product
public function update(Request $request, $id)
{
$product = Product::find($id);
if (!$product) {
return response()->json(['message' => 'Product not found'], 404);
}
$request->validate([
'name' => 'sometimes|string|max:255',
'description' => 'sometimes|string',
'price' => 'sometimes|numeric',
]);
$product->update($request->all());
return response()->json($product);
}
// Delete a product
public function destroy($id)
{
$product = Product::find($id);
if (!$product) {
return response()->json(['message' => 'Product not found'], 404);
}
$product->delete();
return response()->json(['message' => 'Product deleted']);
}
}
Next, let's define the routes for our API. Open the routes/api.php
file and add the following routes:
use App\Http\Controllers\ProductController;
Route::apiResource('products', ProductController::class);
This single line will create all the necessary routes for our CRUD operations.
Now that our API is set up, let's test it using Postman or any other API testing tool.
Send a POST request to /api/products
with the following JSON body:
{
"name": "Laptop",
"description": "A high-end gaming laptop",
"price": 1500.00
}
Send a GET request to /api/products
to retrieve all products.
Send a GET request to /api/products/1
to retrieve a single product.
Send a PUT request to /api/products/1
with the following JSON body:
{
"name": "Updated Laptop",
"price": 1600.00
}
Send a DELETE request to /api/products/1
to delete the product.
Laravel provides a robust error handling mechanism out of the box, but we can customize it to return consistent JSON responses.
The cleanest way is to extract validation logic into a Form Request and override failedValidation()
to customize the response:
php artisan make:request StoreProductRequest
In app/Http/Requests/StoreProductRequest.php
:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class StoreProductRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|string|max:255',
'description' => 'required|string',
'price' => 'required|numeric',
];
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json([
'error' => 'Validation Error',
'messages' => $validator->errors(),
], 422));
}
}
Then in your controller modify the store
method signature:
use App\Http\Requests\StoreProductRequest;
public function store(StoreProductRequest $request)
{
$product = Product::create($request->validated());
return response()->json($product, 201);
}
If you prefer to keep validation in the controller, use the Validator facade to handle errors manually:
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255',
'description' => 'required|string',
'price' => 'required|numeric',
]);
if ($validator->fails()) {
throw new HttpResponseException(response()->json([
'error' => 'Validation Error',
'messages' => $validator->errors(),
], 422));
}
$product = Product::create($validator->validated());
return response()->json($product, 201);
}
In this tutorial, we created a simple Laravel 12 CRUD API and implemented two approaches to error handling:
failedValidation()
.Happy coding!
Alamgir Khan
Apr 18, 2025 04:13 PM
Kritim Yantra
Apr 19, 2025 11:38 AM