Laravel GraphQL: renaming fields and eager-loading with Lighthouse

In this article you are going to learn tips and tricks that are less obvious. On how you can rename fields by only using build-in directives and use eager-loading to make your own application faster.
What I will not cover
- Setting-up Laravel / Lighthouse
- Explain the basics of GraphQL
So this article assumes you have some basic knowledge about PHP Lighthouse.
Renaming fields
There is this possibility you have good reasons for renaming fields, to hide internal database column prefixes in your GraphQL schema or names from an external service such as SalesForce.
- SalesForce-API uses names like Case.ContactId
- Hide information for security reasons, you want to avoid exposing you’re using SalesForce, WordPress or any other CRM-tool
Why is this important sometimes?
During my time I made applications, or websites for pharmacy companies. It was really important, to never expose underlying technology,
- Hide references of using Elastic Search, MailChimp, Eloqua, SalesForce
- To be compliant with security requirements
OK, now let’s get back to the real topic, altering data and solving problems.
Connecting with a headless CMS

Now, let’s say we pull data from a headless WordPress website.
You can see in the GraphQL-schema, that I am mapping everything without reducing the number of sub-properties.
You will notice that this becomes cumbersome, especially when WordPress tends to use a lot of sub-properties for their WP-json API.

This is we aren’t transforming our data, one solution is by using an invoker with Lighthouse.
While this is a good solution, it does require writing more lines of PHP-code.
Instead it would be much easier to write everything in GraphQL and reduce the nesting with the directive rename.
We start by undoing our previous changes.
Next we apply the special directive rename, to remove all the extra types.
This solution is so easy, that even a front-end developer without the help of a back-end developer can figure how to add more properties to the schema.
Summary:
- We can map multiple levels into a single level
- We have to write less PHP-code
- The response is easier to understand for everyone in your team
- You can hide technical details
Renaming Laravel Relations in your schema
Relations in Laravel is a powerful feature, and Lighthouse made everything easy to integrate our Laravel relations into a schema.

This second example is similar to that of WordPress, but instead we cannot mix the directive rename, but we can only use Laravel hasMany, hasOne, BelongsTo.
Frequently most relations exist of a single word like:
- storyRevisions
- contactList
- PostCategories
All of these relations exist of two words, connected by being camel-case.
=== PHP ===class Post extends Model {
public function postRevisions(): HasMany { ... }
}=== GRAPHQL ===type Post {
# Error: method post_revisions does not exist
post_revisions: [PostRevision] @hasMany
}
When you use hasMany on post_revisions, you’ll see an error that the relation post_revisions doesn’t exist.
{
"errors": [
{
"debugMessage": "Call to undefined relationship [post_revisions] on model [App\\Post].",
"message": "Internal server error",
"extensions": {
"category": "internal"
},
"locations": [
{
"line": 7,
"column": 5
}
],
...
}
}
A possible solution could be renaming the relation with underscores, like in your schema.
=== PHP ===class Post extends Model {
// Bad solution: by using underscores :(
public function post_revisions(): HasMany { ... }
}=== GRAPHQL ===type Post {
post_revisions: [...] @hasMany # Error got fixed
}
Using underscores to define relations, looks a bit weird and clumsy. Unfortunately you want to avoid naming your Laravel relations with underscores. Else when your senior start reading your code, it will give them the cold chills.
Instead you want to take a closer look at the documentation of Lighthouse.

You can add the argument relation, to link to the camel-case method name.
=== PHP ===class Post {
public function storyRevisions(): HasMany { ... }
}
=== GraphQL ===
type Post {
story_revisions: [...] @hasMany(relation: "storyRevisions")
}
Attributes with eager-loading
Laravel makes it easy by its core, to handle eager loading manually with a REST-application. In a controller you can easily end with something like this.
class PostController {
public function index() {
$posts = Post::query()
->with(['comments', 'comments.replies'])
->get();
return view('...', compact('posts'));
}
}
Notable is that I am using the with method, I do this to reduce the number queries and squash them into 3 SQL-queries.
select * from posts
select * from comments WHERE post_id (1,2,3)
select * from replies WHERE comment_id IN (1,2,3,,7,8,...)
What happens without eager loading
Without eager loading, you will introduce the n+1 problem and so for each post an additional SQL-query will be called to retrieve the comment and replies. You will end up with 3-30 or a higher number of queries, like this.
select * from posts
select * from comments WHERE post_id = 1
select * from comments WHERE post_id = 2
select * from comments WHERE post_id = 3
select * from replies WHERE comment_id = 1
select * from replies WHERE comment_id = 2
select * from replies WHERE comment_id = 3
# probably tens or more SQL-queries
This will result in slowing down your application.
But luckily PHP-Lighthouse handles nicely and automatically for you, each time when you use the build-in directives like hasMany, hasOne, belongsTo.

But in rare occasions, you cannot rely on Lighthouse handling eager-loading for you, when you use a get attribute that needs a relation.
=== PHP ===class User {
public function address(): HasOne { ... }
public function getFullAddresssAttribute() {
$address = $this->mainAddress;
return $adddress ? $address->street.' '. $address->nr : null;
}
}
My method getFullAddressAttribute uses the relation address, but when someone retrieves the field full_address. Lighthouse cannot detect by itself it relies on the relation address.
=== GraphQL: user.graphql ===type User {
id: ID!
# full address in one string value,
# that needs the relation address.
full_address: String
}type Query {
users: [User!]
}=== React Apollo ===const QUERY = gql`query {
users {
first_name
fUll_address # Korenmarkt XXX, Ghent 9000
}
}`;
Problem
To summarize the set-up above, each time GraphQL asks for the full address, it needs the relation address. But Lighthouse doesn’t know it needs that relation.
This results in the n+1 problem because we are not using the hasOne relation on the full address. So when you are returning 1000 users, then a thousand additional SQL queries are called for each user.
Solution
We need to apply the directive with, to tell Lighthouse we need to relation between the attribute full address and the relation address.
=== GRAPHQL ===
type User {
id: ID!
full_address: String @with(relation: "address")
}
I am not going into more details, this should all be very explanatory if you used $model->with before.
Documentation: https://lighthouse-php.com/5/api-reference/directives.html#with
Rename sub-properties of a relation
This is a short example and is similar to the example of WordPress, but we are mixing multiple directives, to retrieve a sub-property, from a relation without nesting types.
type Address {
street: String
}type User {
id: ID!
address: Address
}
Again you can easily start seeing, we are nesting types. But if you want to flat everything into a single type. You can use rename and with to eager load the address.
type User {
id: ID!
street: String
@rename(attribute: "address.street") @with(relation: "address")
nr: String
@rename(attribute: "address.nr") @with(relation: "address")
}
Use a method as field
As the last example, we can use the directive method to call a function from your model.
type User {
crm_contact: JSON! @method(name: "getSalesForceContact")
}
That’s all
This is everything I had to cover, if you want me to have an in-depth about other topics around PHP Lighthouse please leave a comment.