participant "REST API" as R participant "GraphQL API" as G
== REST: multiple endpoints == M -> R : ① GET /users/123 R --> M : ② user info M -> R : ③ GET /users/123/posts R --> M : ④ posts M -> R : ⑤ GET /users/123/followers R --> M : ⑥ followers
note right of M : 3 requests, possible data dup
== GraphQL: one endpoint == W -> G : ⑦ POST /graphql\n{ user(id:123) { name, posts { title }, followers { name } } } G --> W : ⑧ return all fields in one response
note right of W : 1 request, precise fields
@enduml
REST pain points:
over-fetching — /users/123 returns all fields, client only uses name
under-fetching — need user + posts + followers, 3 requests
multi-client maintenance — Mobile / Web need different endpoints
GraphQL advantages:
one request, precise fields
strongly typed schema — client SDK auto-generated
aggregate microservices — Apollo Federation across services
actor "Client" as Client participant "GraphQL Server" as GQL participant "Resolver: User" as R1 participant "Resolver: User.posts" as R2 participant "Resolver: Post.author" as R3 participant "Resolver: Post.comments" as R4 participant "Resolver: Comment.author" as R5 database "DB" as DB
Client -> GQL : ① query { user(id:1) { name, posts { title, author { name }, comments { body, author { name } } } } }
GQL -> R1 : ② resolve User R1 -> DB : ③ SELECT * FROM users WHERE id=1 DB --> R1 : ④ user R1 --> GQL : ⑤ user
par parallel resolve posts[] GQL -> R2 : ⑥ resolve User.posts R2 -> DB : ⑦ SELECT * FROM posts WHERE user_id=1 DB --> R2 : ⑧ posts R2 --> GQL : ⑨ posts end
loop each post par post.author GQL -> R3 : ⑩ resolve Post.author R3 -> DB : ⑪ SELECT * FROM users WHERE id=? DB --> R3 : ⑫ author and post.comments GQL -> R4 : ⑬ resolve Post.comments R4 -> DB : ⑭ SELECT * FROM comments WHERE post_id=? DB --> R4 : ⑮ comments end loop each comment GQL -> R5 : ⑯ resolve Comment.author R5 -> DB : ⑰ SELECT * FROM users WHERE id=? DB --> R5 : ⑱ author end end
@strawberry.type classPost: id: strawberry.ID @strawberry.field asyncdefauthor(self, info) -> 'User': # use info.context pre-loaded data returnawait info.context['user_loader'].load(self.author_id)
# create loader asyncdefget_user_by_id(user_ids: list[int]) -> list[User]: users = await db.query("SELECT * FROM users WHERE id = ANY($1)", [user_ids]) return [User.from_row(u) for u in users]
@startuml title "Apollo Federation - Composed GraphQL"
actor "Client" as Client participant "Apollo Gateway" as Gateway participant "Users Subgraph" as Users participant "Posts Subgraph" as Posts participant "Reviews Subgraph" as Reviews database "Users DB" as UDB database "Posts DB" as PDB database "Reviews DB" as RDB
GQL -> CA : ② calculate query cost CA -> CA : ③ nesting depth = 5 CA -> CA : ④ field count = 50 CA -> CA : ⑤ alias dup = 10 CA -> CA : ⑥ total cost = 1500
alt Cost > 1000 CA --> GQL : ⑦ reject (too complex) GQL --> Client : ⑧ 400 Bad Request else Cost <= 1000 GQL -> RL : ⑨ check rate limit alt Rate exceeded RL --> GQL : ⑩ reject GQL --> Client : ⑪ 429 Too Many Requests else OK GQL -> GQL : ⑫ execute GQL --> Client : ⑬ 200 end end
Minimum start: Apollo Server + DataLoader + depth limit. Production: add Federation + Subscription + Apollo Studio monitoring.
Remember: GraphQL is not a REST replacement, it solves over-fetching / under-fetching. Simple CRUD uses REST simpler. Complex aggregation / multi-client adaptation uses GraphQL. Don’t use GraphQL for the sake of using GraphQL.