Support the ongoing development of Laravel.io →
Article Hero Image

How I Reduced Laravel N+1 Queries by 83% Using JSON Aggregation

7 Jan, 2026 3 min read 73 views

Photo by Markus Spiske on Unsplash

The Problem: N+1 Queries Are Killing Your API Performance

If you've worked with Laravel's Eloquent ORM, you've probably encountered the N+1 query problem. Even with eager loading (with()), Laravel generates multiple queries when loading related collections.

Here's a typical scenario:

// This looks innocent, but...
$partners = Partner::with(['profile', 'country', 'promocodes'])->get();

// Laravel generates:
// 1 query for partners
// 1 query for profiles  
// 1 query for countries
// 1 query for promocodes
// Total: 4 queries

For 2,000 partners, this pattern generates 43 queries and takes 287ms with 44MB memory usage.

The Traditional Solution: Eager Loading Isn't Enough

Eager loading helps, but it still generates separate queries for each relationship. For read-heavy APIs and dashboards, this becomes a bottleneck:

  • Multiple database round-trips
  • Large result sets to hydrate
  • High memory consumption
  • Unpredictable query counts as relations grow

A Better Solution: JSON Aggregation

I built laravel-aggregated-queries to solve this problem using database-native JSON aggregation (MySQL 8+ / PostgreSQL 12+).

Instead of multiple queries, we leverage JSON_OBJECT() and JSON_ARRAYAGG() to combine everything into a single SQL statement:

use Rgalstyan\LaravelAggregatedQueries\Traits\HasAggregatedQueries;

class PartnerRepository extends Repository
{
    use HasAggregatedQueries;
    
    public function getAllWithRelations()
    {
        return $this->aggregatedQuery()
            ->withJsonRelation('profile', ['id', 'name', 'email'])
            ->withJsonRelation('country', ['id', 'name', 'code'])
            ->withJsonCollection('promocodes', ['id', 'code', 'discount'])
            ->get();
    }
}

Result: 1 query instead of 4.

Real-World Benchmark Results

I tested this approach with 2,000 partners, each having a profile, country, and multiple promocodes:

Approach Time Memory Queries
Traditional Eloquent 287ms 44MB 43
JSON Aggregation 48ms 3.4MB 1

Improvements:

  • âš¡ 83.3% faster execution time
  • 💾 92.3% less memory consumption
  • 🔢 97.7% fewer queries (43 → 1)

When Should You Use This?

This approach is perfect for:

✅ Read-heavy endpoints - APIs, dashboards, reports
✅ Multiple collections - 3+ relations per query
✅ DTO/array responses - No need for full Eloquent models
✅ Predictable performance - Exact query count guaranteed

Not recommended for:

  • Write-heavy operations (use standard Eloquent)
  • Complex business logic requiring Eloquent models
  • Simple single-relation queries (eager loading is fine)

How It Works Under The Hood

The package generates optimized SQL like this:

SELECT 
    partners.*,
    JSON_OBJECT('id', profile.id, 'name', profile.name) AS profile,
    JSON_OBJECT('id', country.id, 'name', country.name) AS country,
    (SELECT JSON_ARRAYAGG(
        JSON_OBJECT('id', id, 'code', code, 'discount', discount)
    ) FROM promocodes WHERE partner_id = partners.id) AS promocodes
FROM partners
LEFT JOIN profiles AS profile ON profile.id = partners.profile_id
LEFT JOIN countries AS country ON country.id = partners.country_id

The database does all the heavy lifting, returning exactly one row per partner with all relations pre-aggregated.

Installation & Usage

Install via Composer:

composer require rgalstyan/laravel-aggregated-queries

Add the trait to your repository:

use Rgalstyan\LaravelAggregatedQueries\Traits\HasAggregatedQueries;

class YourRepository extends Repository
{
    use HasAggregatedQueries;
    
    public function getWithRelations()
    {
        return $this->aggregatedQuery()
            ->withJsonRelation('relation')
            ->withJsonCollection('collection')
            ->get();
    }
}

That's it! No configuration needed.

Database Support

  • ✅ MySQL 8.0+
  • ✅ PostgreSQL 12.0+
  • ✅ Laravel 10, 11, 12
  • ✅ PHP 8.2+

Why I Built This

I was working on a B2B platform connecting restaurants and travel agencies. The order listing API was generating 40+ queries and taking 300ms+ per request.

After implementing JSON aggregation, we reduced it to 1 query and 60ms - a 5x performance improvement that made the difference between a sluggish and snappy user experience.

I initially built this for Symfony (where the same problem exists with Doctrine), and the Laravel version followed based on community interest.

What's Next?

The package is MIT licensed and production-ready with:

  • Comprehensive test suite (PHPUnit)
  • PHPStan level 9 compliance
  • GitHub Actions CI/CD
  • Full documentation

Check it out:

I'm actively maintaining it and open to feedback, issues, and PRs. If you're dealing with N+1 query problems in read-heavy scenarios, give it a try and let me know how it works for you!

Conclusion

JSON aggregation won't replace Eloquent's eager loading for everything, but for read-heavy APIs where performance matters, it's a game-changer.

Next time you see 40 queries in your debug toolbar, consider whether your use case fits this pattern. Your database can do more than you think.

Happy coding! 🚀

Last updated 1 day ago.
1
Like this article? Let the author know and give them a clap!
rgalstyan (Raz Galstyan) Senior Backend Engineer

Other articles you might like

Article Hero Image December 18th 2025

Formatting PHP Code with PHP CS Fixer

Introduction Maintaining a consistent code style is a key aspect of software and web development. It...

Read article
Article Hero Image November 24th 2025

The Difference Between ?: and ?? in PHP

Introduction In PHP, I often see the ternary operator (?:) and null coalescing operator (??) being u...

Read article
Article Hero Image November 20th 2025

Immutable and Mutable Dates in PHP

Introduction When working with dates in PHP, it's important to understand the difference between mut...

Read article

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2026 Laravel.io - All rights reserved.