GraphQL vs REST — what problem GraphQL solves
Concept
GraphQL vs REST — two different approaches to API design. REST exposes resources; GraphQL exposes a query language.
The problem GraphQL solves: REST APIs suffer from over-fetching and under-fetching.
- Over-fetching:
GET /users/42returns 20 fields, but the mobile app only needsnameandavatar. Wasted bandwidth. - Under-fetching: To show a user's profile with their orders, you need
GET /users/42, thenGET /users/42/orders(N+1 requests). Multiple round trips. - Rigid endpoints: REST endpoints return fixed shapes. You can't ask for a custom subset without building a new endpoint.
What GraphQL is: A query language for APIs. The client specifies exactly what it needs — field by field, nested relationships — in a single request.
GraphQL request: A POST to a single endpoint (/graphql) with a query in the body. The server parses the query, resolves each field via "resolvers," and returns exactly what was asked for.
GraphQL strengths:
- Client controls the shape of the response.
- One request can fetch complex, nested data (user + orders + products).
- Self-documenting via the schema (introspection).
- Strong typing (schema defines every type).
GraphQL weaknesses:
- Caching is harder (all requests are POSTs to one URL — CDN can't cache).
- Complex queries can be expensive (N+1 on resolvers without DataLoader).
- Steeper learning curve for server developers.
- File uploads are non-standard.
- Over-fetching problem moves to the server (clients can ask for anything — must add query depth/complexity limits).
When to use REST: Public APIs, simple CRUD, when caching is important, when you want clear URL semantics. When to use GraphQL: Complex UIs with varying data needs, mobile apps (bandwidth-sensitive), team where frontend and backend move independently.
Code Example
<?php
// REST — fixed response shape, multiple round trips for related data
// GET /users/42 → returns user
// GET /users/42/orders → returns orders
// GET /orders/7/items → returns items
// 3 round trips to build one UI component!
// GRAPHQL — one request, client specifies exact shape
// POST /graphql
// Body:
// query {
// user(id: 42) {
// name
// avatar
// orders(status: "pending") {
// id
// total
// items {
// productName
// quantity
// }
// }
// }
// }
// One request — returns exactly what was asked
// Laravel GraphQL with Lighthouse (graphql-lighthouse/lighthouse)
// Schema definition (schema.graphql):
/*
type Query {
user(id: ID! @eq): User @find
users: [User!]! @all
}
type User {
id: ID!
name: String!
email: String!
orders: [Order!]! @hasMany
}
type Order {
id: ID!
total: Float!
status: String!
items: [OrderItem!]! @hasMany
}
type Mutation {
createUser(name: String!, email: String!): User @create
deleteUser(id: ID!): User @delete
}
*/
// PHP resolver (for custom logic)
namespace App\GraphQL\Queries;
class UserResolver
{
public function resolve($root, array $args): mixed
{
return User::findOrFail($args['id']);
}
}
// N+1 PROBLEM in GraphQL — same as Eloquent, solved with DataLoader
// Without DataLoader: resolving 10 users' orders → 10 queries
// With DataLoader: batches all order lookups → 1 query
// Lighthouse handles this automatically with @hasMany (uses batch loading)
// COMPARISON TABLE:
// REST:
// Multiple endpoints for different resources
// Server defines response shape
// GET /users → easy to cache
// Multiple requests for related data
// Simple to implement CRUD
// GraphQL:
// Single /graphql endpoint
// Client defines response shape
// POST /graphql → harder to cache
// One request for complex nested data
// Resolver functions for each field type
// Schema is the contract
// HYBRID APPROACH (common in Laravel):
// Use REST for simple CRUD and public APIs
// Use GraphQL for complex frontend data fetching
// Some teams use both: REST for mutations, GraphQL for queries
// REST vs GraphQL over-fetching example:
// REST: GET /users/42 → {id, name, email, phone, address, avatar, created_at, ...all 20 fields}
// Mobile app uses: name, avatar only — 18 fields wasted
// GraphQL: query { user(id: 42) { name avatar } }
// → {name: "Alice", avatar: "https://..."} — exactly 2 fields