How to solve the n+1 problem created by fractal

Unseen Heroes
May 22, 2018 · 3 min read

The popular package, thephpleague/fractal aims to protect your API consumers from output inconsistencies and also provide an elegant way to include relationships for complex data structures.

example.com?include=foo,foo.bar,foo.bar.baz,bla 
for 100 records per page == 400 queries!

The problem with the includes is that all relations are being lazily loaded.

TL;DR use Laravel 5.5+ Eloquent Resources and Spatie’s query builder

Image for post
Image for post
The Fractal transformer

The , getters were created manually inside the model, in order to decouple database schema from code.

For this example, the includes may look like:

example.com/user/101/reviews?include=product,product.merchant
Image for post
Image for post
example.com/user/101/reviews?include=product,product.merchant

Besides the nested data key, the additional database queries are killing the performance.

There are some workarounds, for example, we could use eagerly loaded relations, but the question is “how many?”. What happens when the front-end requires more includes? Do we eager load everything, even nested relations?

Lazy eager load means more than one request and, moreover, you have to implement it manually, to override the class, , after fractal has checked that a given include is valid.

Joins are too expensive, especially for large tables.

The solution I found consists of this couple:

Laravel 5.5 introduced Eloquent Resources, which basically are a sort of native transformers on steroids: https://laravel.com/docs/5.6/eloquent-resources#conditional-relationships

Spatie’s query builder https://github.com/spatie/laravel-query-builder whose ?include=product,product.merchant enables you to get rid of the fractal package.

Why on steroids? The conditional relationship enables the application to eagerly load JUST the requested relationships, namely and only when they are requested.

Let’s suppose the consumer does not require the nested relation

example.com/user/101/reviews?include=product

Then, the relation is not loaded and the key is missing entirely from the generated output! How cool is that?

example.com/user/101/reviews?include=not-whitelisted.nested-relation

Returns status code 400, with the message: Given include(s) `not-whitelisted.nested-relation` are not allowed. Allowed include(s) are `product, product.merchant`

Another benefit of Laravel’s Resource is that you can structure the output better. No more nested keys on every included relation

Spatie’s query builder can also filter, sort and restrict the nested includes (which fractal can’t do). Moreover, the statement won’t fool the application to display all protected resources.

Image for post
Image for post
Review, Product and Merchant resources
Image for post
Image for post
The controller
Image for post
Image for post
Spatie query builder + JSON API paginate
Image for post
Image for post
The final output
Image for post
Image for post
Hooray! The queries

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store