Laravel 12 CRUD API with Error Handling

Author

Kritim Yantra

Mar 23, 2025

Laravel 12 CRUD API with Error Handling

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.

Step 1: Setting Up Laravel

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

Step 2: Database Configuration

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

Step 3: Creating the Model and Migration

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

Step 4: Creating the Controller

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']);
    }
}

Step 5: Defining Routes

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.

Step 6: Testing the API

Now that our API is set up, let's test it using Postman or any other API testing tool.

Create a Product (POST /api/products)

Send a POST request to /api/products with the following JSON body:

{
    "name": "Laptop",
    "description": "A high-end gaming laptop",
    "price": 1500.00
}

Get All Products (GET /api/products)

Send a GET request to /api/products to retrieve all products.

Get a Single Product (GET /api/products/{id})

Send a GET request to /api/products/1 to retrieve a single product.

Update a Product (PUT /api/products/{id})

Send a PUT request to /api/products/1 with the following JSON body:

{
    "name": "Updated Laptop",
    "price": 1600.00
}
  

Delete a Product (DELETE /api/products/{id})

Send a DELETE request to /api/products/1 to delete the product.

Step 7: Error Handling

Laravel provides a robust error handling mechanism out of the box, but we can customize it to return consistent JSON responses.

7.1 Using a Custom Form Request

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);
}

7.2 Manual Validation Inside Controller

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);
}

Conclusion

In this tutorial, we created a simple Laravel 12 CRUD API and implemented two approaches to error handling:

  • Using Form Requests for clean separation and automatic invocation of failedValidation().
  • Manual validation inside controllers with the Validator facade.
Both methods ensure consistent JSON responses and make your API more maintainable and user-friendly.

Happy coding!

Tags

Comments

Alamgir Khan

Alamgir Khan

Apr 18, 2025 04:13 PM

Where you have shown the errors message examples of the following code. $request->validate([ 'name' => 'required|string|max:255', 'description' => 'required|string', 'price' => 'required|numeric', ]);
K

Kritim Yantra

Apr 19, 2025 11:38 AM

Hi Alamgir! 👋 Great question. Laravel automatically returns validation error messages in JSON format when the $request->validate() method fails.

Please log in to post a comment:

Sign in with Google

Related Posts