After REST: Trying Out GraphQL
While solving a real over-fetching problem with GraphQL, I weigh where REST is still sufficient and where GraphQL actually makes sense.
GraphQL had been on my radar for years, but I kept putting it off with the “REST already works” feeling. Mid-year on one project I ran into a concrete problem, and solving that problem actually required giving GraphQL a real try. Now I’m writing up both what I found and where REST still holds up better.
The concrete problem: too many requests for a single mobile screen
An app screen displayed: a user profile, their last three orders, and the product names within each order. With REST we were making three separate endpoint calls to assemble this:
GET /api/user/42→ profileGET /api/users/42/orders?limit=3→ ordersGET /api/orders/{id}/itemsfor each order (at least three more requests)
Five to six HTTP requests in total. Each one waits for a response, each one involves JSON parsing. Network latency was slowing down the screen’s load time noticeably.
On top of that, every endpoint was bringing back more data than we needed. The order list included order date, payment method, shipping address — none of which appeared on the screen. We had to fetch, transfer, and parse all that extra data anyway.
REST has standard solutions for these problems: eager loading via an include parameter, custom endpoints, API Gateway aggregation… But all of those put extra burden on the server side, and every new screen requirement calls for a new endpoint or a new parameter arrangement.
What GraphQL is
GraphQL (Graph Query Language) is an API query language developed by Facebook and open-sourced in 2015. It is not a protocol like REST; it is a query mechanism where the client specifies exactly what it wants.
A single endpoint (/graphql) returns different data depending on the query the client sends:
query UserDashboard($userId: ID!) {
user(id: $userId) {
name
email
orders(limit: 3) {
id
status
items {
productName
}
}
}
}
This single query fetches in one round-trip what five or six REST requests used to cover. The client declares exactly which fields it needs; the server returns only those fields.
Setting up GraphQL in Laravel with PHP
The rebing/graphql-laravel package makes standing up a GraphQL server in Laravel relatively straightforward:
composer require rebing/graphql-laravel
Type definition:
<?php
use Rebing\GraphQL\Support\Type as GraphQLType;
use GraphQL\Type\Definition\Type;
class UserType extends GraphQLType
{
protected $attributes = [
'name' => 'User',
'model' => \App\Models\User::class,
];
public function fields(): array
{
return [
'id' => ['type' => Type::nonNull(Type::id())],
'name' => ['type' => Type::nonNull(Type::string())],
'email' => ['type' => Type::nonNull(Type::string())],
];
}
}
Query definition:
<?php
use Rebing\GraphQL\Support\Query;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
class UserQuery extends Query
{
protected $attributes = ['name' => 'user'];
public function type(): Type
{
return GraphQL::type('User');
}
public function args(): array
{
return [
'id' => ['type' => Type::nonNull(Type::id())],
];
}
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo)
{
return User::findOrFail($args['id']);
}
}
The N+1 problem: GraphQL’s classic trap
The moment you start using GraphQL, the N+1 problem hits you in the face. When you request a user’s orders and then the items within each order, the resolver can open a separate database query for every single order.
In the PHP ecosystem there are dataloader libraries to address this. Tools like luckydonald/php-odata-query or overblog/dataloader-php batch the requests. What you solved with with() in REST/Eloquent needs more deliberate management in GraphQL.
An honest comparison
When GraphQL makes sense:
- Multiple clients (web, mobile, third-party) share the same API and each has different data needs.
- Over-fetching and under-fetching are creating a real performance problem.
- You want to open new queries for rapidly changing UI requirements without touching the backend.
Where REST still holds up better:
- There is a single client and the data requirements are relatively predictable.
- The workload is predominantly simple CRUD operations.
- Caching is critical: REST endpoints cache easily with HTTP caching, while GraphQL POST requests make that considerably more painful.
- The team is not yet mature enough to invest in the GraphQL toolchain.
At the end of this experiment I decided to reach for GraphQL specifically in the places where over-fetching is a genuine problem, rather than adopting it everywhere. REST and GraphQL can coexist quite comfortably within the same service.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.