DEV Community

Cover image for Mastering Obect Relational Mapping In Laravel: Spotlight On AnyGrid JS
Gugulethu Nyoni
Gugulethu Nyoni

Posted on

Mastering Obect Relational Mapping In Laravel: Spotlight On AnyGrid JS

In this article, we'll explore how to handle rendering relational data in Laravel. Imagine an e-commerce scenario where products are linked to categories. When displaying product details, you'd want to show the category name alongside them in a clean, user-friendly way. Or think of a blog where each post includes its comments right below the content.

This is where Object-Relational Mapping (ORM) comes in. In this guide, I'll walk you through how to implement it seamlessly and elegantly in Laravel, using AnyGridJS.

  1. Project Setup: Laravel Installation

Before integrating AnyGrid, ensure Laravel is installed. Follow the latest official installation guide here:

πŸ‘‰ Laravel Installation Guide

Basic Installation (For Laravel 10+):

Ensure you have PHP (β‰₯ 8.1), Composer, and Node.js installed.

Create a new Laravel project:

composer create-project --prefer-dist laravel/laravel anygrid-laravel
Enter fullscreen mode Exit fullscreen mode

Navigate into the project folder:

cd anygrid-laravel
Enter fullscreen mode Exit fullscreen mode

Start the local development server:

php artisan serve
Enter fullscreen mode Exit fullscreen mode

🎨 Your Laravel app will be accessible at http://127.0.0.1:8000.

  1. Install AnyGrid JS (CDN Method)

We’ll use the CDN method to quickly integrate AnyGrid JS into our Laravel Blade views.

In your resources/views/layouts/app.blade.php or any other Blade layout file you want to use, add the following inside the <head> tag:

<!-- AnyGrid CSS -->
<link rel="stylesheet" href="https://unpkg.com/anygridcss@1.0.1/anyGrid.css" anygrid-style>

<!-- AnyGrid JS -->
<script src="https://cdn.jsdelivr.net/npm/anygridjs@1.0.9/anyGrid.global.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Ensure you are using the latest of these packages.

🟑 3. Set Up Table Schema and Model Relationships

In this section, we'll create the database structure for an e-commerce scenario with Product and ProductCategory models, demonstrating a One-to-Many relationship.

Create Models with Migrations

Generate ProductCategory and Product Models:

Run the following commands to create models with migrations:

php artisan make:model ProductCategory -m
php artisan make:model Product -m
Enter fullscreen mode Exit fullscreen mode

Update Migrations:

  • ProductCategory Migration: (database/migrations/YYYY_MM_DD_create_product_categories_table.php)
public function up()
{
    Schema::create('product_categories', function (Blueprint $table) {
        $table->id();
        $table->string('name')->unique();
        $table->text('description')->nullable();
        $table->timestamps();
    });
}
Enter fullscreen mode Exit fullscreen mode
  • Product Migration: (database/migrations/YYYY_MM_DD_create_products_table.php)
public function up()
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->text('description')->nullable();
        $table->decimal('price', 10, 2);
        $table->foreignId('product_category_id')->constrained()->onDelete('cascade');
        $table->timestamps();
    });
}
Enter fullscreen mode Exit fullscreen mode

Model Relationships

  • ProductCategory Model: (app/Models/ProductCategory.php)
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ProductCategory extends Model
{
    protected $fillable = ['name', 'description'];

    public function products()
    {
        return $this->hasMany(Product::class);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Product Model: (app/Models/Product.php)
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $fillable = ['name', 'description', 'price', 'product_category_id'];

    public function category()
    {
        return $this->belongsTo(ProductCategory::class, 'product_category_id');
    }
}
Enter fullscreen mode Exit fullscreen mode

Run Migrations

Apply the database changes:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

βœ… This creates two tables: product_categories and products.

πŸ’‘ Next Step: With the database schema and relationships set up, we'll move on to the controller logic for rendering products.

🟒 4. Controller Logic for Rendering Products

With our models and database ready, we'll now build a controller to retrieve and render products efficiently while avoiding the N+1 query problem using Laravel's eager loading.

The N+1 query problem occurs when a database query retrieves a list of records, and then for each record, it runs additional queries to retrieve related data. This results in one query to fetch the main data (the "1" query) and then one additional query for each related record (the "N" queries). The N+1 query is non performant and inefficient because it leads to a large number of database queries, which increases the load on the database and slows down performance.

Create Product Controller

Generate the controller:

php artisan make:controller ProductController
Enter fullscreen mode Exit fullscreen mode

Open app/Http/Controllers/ProductController.php and add the following:

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    // Fetch all products with categories
    public function index()
    {
        // Eager loading to avoid N+1 problem
        $products = Product::with('category')->get();

        return response()->json($products);
    }
}
Enter fullscreen mode Exit fullscreen mode

N+1 Query Problem: Without Eager Loading

If you fetch products without eager loading:

$products = Product::all();
foreach ($products as $product) {
    echo $product->category->name;
}
Enter fullscreen mode Exit fullscreen mode

πŸ”΄ Generated SQL (Inefficient):

-- Fetch products (1 query)
SELECT  FROM products;

-- Separate query for each product's category (N queries)
SELECT  FROM product_categories WHERE id = 1;
SELECT  FROM product_categories WHERE id = 2;
SELECT  FROM product_categories WHERE id = 3;
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ Resulting Data (Without Eager Loading):

[
    {
        "id": 1,
        "name": "Smartphone",
        "description": "Latest Android smartphone.",
        "price": "699.99",
        "product_category_id": 1
    },
    {
        "id": 2,
        "name": "Laptop",
        "description": "High-performance laptop.",
        "price": "1299.99",
        "product_category_id": 1
    }
]
Enter fullscreen mode Exit fullscreen mode

Problem: To access $product->category->name, Laravel runs a separate query for each product, causing multiple database hits.

Solution: With Eager Loading

Update the controller to use Eager Loading:

$products = Product::with('category')->get();
Enter fullscreen mode Exit fullscreen mode

Generated SQL (Efficient):

-- Single query for products
SELECT  FROM products;

-- Single query for all related categories
SELECT  FROM product_categories WHERE id IN (1, 2, 3);
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ Resulting Data (With Eager Loading):

[
    {
        "id": 1,
        "name": "Smartphone",
        "description": "Latest Android smartphone.",
        "price": "699.99",
        "product_category_id": 1,
        "category": {
            "id": 1,
            "name": "Electronics",
            "description": "Gadgets and devices"
        }
    },
    {
        "id": 2,
        "name": "Laptop",
        "description": "High-performance laptop.",
        "price": "1299.99",
        "product_category_id": 1,
        "category": {
            "id": 1,
            "name": "Electronics",
            "description": "Gadgets and devices"
        }
    }
]
Enter fullscreen mode Exit fullscreen mode

🟣 Performance Comparison:

Aspect Without Eager Loading With Eager Loading
Number of Queries 1 + N (1 per product) 2 (Products + Categories)
Query Efficiency Slow (multiple hits) Fast (batched)
Result Structure Flat (No relations) Nested (Relations loaded)
Memory Usage Higher Lower

Loading multiple relationships:

Product::with(['category', 'supplier'])->get();
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Next Step: Now that products load efficiently, we'll move on to Integrating AnyGridJS to display this data in the frontend.

🟒 5. Creating the Product View with AnyGridJS

Update Controller to Pass Data to View

In ProductController.php, update the index method to pass products to the view:

public function index()
{
    $products = Product::with('category')->get();
    return view('shop.products', compact('products'));
}
Enter fullscreen mode Exit fullscreen mode

Create the Blade View for Displaying Products

Create the resources/views/shop/products.blade.php file:

@extends('layouts.app')
@section('content')
    <h1>Product Listings</h1>

    <!-- AnyGrid Container:: The data table will be rendered here -->
    <div id="anygrid"></div>

    <script type="module">
        // Pass Laravel data to JavaScript
        const data = @json($products);

        // Define columns for AnyGrid
        const columns = [
    { name: 'id', header: 'ID', sortable: true },
    { name: 'name', header: 'Product Name', sortable: true },
    { name: 'price', header: 'Price', render: (value) => `R ${value}` },
    { name: 'category_id', header: 'Category', render: (value, row) => row.category ? row.category.name : 'N/A' },
    { name: 'created_at', header: 'Created Date', sortable: true,
    {
        name: 'actions',
        header: 'Actions',
        actions: [
            {
                label: 'EDIT',
                url: 'products/edit/{id}',
                class: 'edit',
                id: 'edit-{id}',
            },
            {
                label: 'DELETE',
                url: 'products/delete/{id}',
                class: 'delete',
                id: 'delete-{id}',
                confirm: true,
            },
            {
                label: 'VIEW',
                url: 'products/view/{id}',
                class: 'view',
                id: 'view-{id}',
            },

            },

        ]
    }
];


        // AnyGrid features and settings
        const features = {
            initialItemsPerPage: 20,
            csvExport: true,
            excelExport: true,
            theme: 'dark'
        };

        // Initialize AnyGrid
        new AnyGrid(data, columns, features);
    </script>


@endsection
Enter fullscreen mode Exit fullscreen mode

Update Routes to Load the View

In routes/web.php:

use App\Http\Controllers\ProductController;

Route::get('/products', [ProductController::class, 'index']);
Enter fullscreen mode Exit fullscreen mode

Final Result

Relational Data Rendering in Laravel and AnyGrid JS

Conclusion:

In this tutorial, we covered the essentials of integrating AnyGridJS with a Laravel application to display relational data dynamically. We walked through:

  1. Setting up Laravel with the required models and migrations for an ecommerce scenario (products and categories).
  2. Using eager loading to efficiently fetch related data and avoid the N+1 query problem.
  3. Integrating AnyGridJS to display data in a user-friendly grid with features like pagination, sorting, and export options.
  4. Configuring action buttons for editing, deleting, and viewing products directly within the grid.

With these foundational steps, you can now build more interactive and performant data tables for your Laravel applications.

Learn More

To learn more about AnyGridJS, visit our GitHub repository.

You can also visit the AnyGridJS website for further documentation and resources.

References

Top comments (0)