Dev Talks

Web Development Tutorials

Laravel REST API Part 5: Add Products List API
Laravel

Laravel REST API Part 5: Add Products List API

Published on Dec 3, 2024

After we added our migrations and authentication and also seeded some data into the database. We are going to create products list api. We will try to simulate exactly what we do in a real life scenario, so you will be able to deliver high quality and well performing API's after this short series.

In a real-world development scenario, requirements for a feature are typically written on tools like Jira. Let's imagine a situation where the requirement specifies the creation of an API to display a list of products. The API should allow filtering products by name, price range, stock range, and category ID. When discussing those requirements with the team, it turns out that we will need an api that might look like this: 


[GET] /api/products

REQUEST:
[
	product_name,
	price_from,
	price_to,
	stock_from,
	stock_to,
	category_id
]

RESPONSE:
[
	{
		id,
		name,
		price,
		stock,
		created_at,
		category: {
			id,
			name
		}
	}
]


We are now have a clear requirements, let's start building our API


Step 1: Setting Up the Route

We'll start by defining a protected route for fetching the product list. Here's how the route should look:


use App\Http\Controllers\ProductController;

Route::middleware('auth:sanctum')->group(function () {
    Route::get('products', [ProductController::class, 'index'])->name('products');
});


This route is protected by middleware to ensure only authenticated users can access it.


Step 2: Creating the ProductController

Next, create a ProductController with an index method to handle the request:


php artisan make:controller ProductController


In the index method, we'll retrieve and return all products. Initially, it may look like this:


use App\Models\Product;

public function index(Request $request)
{
    $products = Product::all();
    return response()->json($products);
}


Step 3: Using API Resources for Structured Responses

To make the responses organized and readable, we’ll use Laravel API resources. Create resources for Product and Category:


php artisan make:resource ProductResource
php artisan make:resource CategoryResource


In ProductResource, define the structure for the product response:


use Illuminate\Http\Resources\Json\JsonResource;

class ProductResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'price' => $this->price,
            'stock' => $this->stock,
            'created_at' => $this->created_at,
            'category' => new CategoryResource($this->whenLoaded('category')),
        ];
    }
}


Note: If you did not provide whenLoaded categories will be lazy loaded and this will cause n+1 problem, meaning that you will have a new query to the database with each product you have trying to get it's category + the original query. This will happen if you did not eager load it in the main query.

In CategoryResource, define the structure for the category response:


use Illuminate\Http\Resources\Json\JsonResource;

class CategoryResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
        ];
    }
}


Step 4: Adding Filters to the Query

To allow filtering, we modify the index method in ProductController:


public function index(Request $request)
{
    $query = Product::query();

    // Filtering by name
    if ($request->has('product_name')) {
        $query->where('name', 'like', '%' . $request->product_name . '%');
    }

    // Filtering by price range
    if ($request->has('price_from')) {
        $query->where('price', '>=', $request->price_from);
    }

    if ($request->has('price_to')) {
        $query->where('price', '<=', $request->price_to);
    }

    // Filtering by stock range
    if ($request->has('stock_from')) {
        $query->where('stock', '>=', $request->stock_from);
    }

    if ($request->has('stock_to')) {
        $query->where('stock', '<=', $request->stock_to);
    }

    // Filtering by category ID
    if ($request->has('category_id')) {
        $query->where('category_id', $request->category_id);
    }

    // Eager loading the category relation
    $query->with('category');

    // Ordering by name
    $query->orderBy('name');

    // Paginating the results
    $products = $query->paginate(15);

    return ProductResource::collection($products);
}


Step 5: Testing the API

Use Postman or another API testing tool to test the endpoint. Here’s how to set up the request:

  1. URL: https://localhost/api/products
  2. Method: GET
  3. Headers: Include the authorization token (if using Sanctum).
  4. Query Parameters: Add filters like product_name, price_from, price_to, stock_from, stock_to, and category_id.
  5. Authorization: Don't forget to add Bearer {token}


Conclusion

Our API is now ready, complete with filters, eager loading, and proper structuring using API resources. In a follow-up article, we’ll explore writing unit tests for this API and refactoring the controller for better readability and reusability.

If you are a visual learner and prefer video tutorials you can check it on our youtube channel

Stay tuned for more Laravel tutorials!