1. Introduction
GraphQL is often marketed as the “REST killer,” but that’s not the full story. It’s a query language for APIs that gives clients the power to request exactly the data they need. Behind the hype, GraphQL is still just requests and responses over a transport protocol like HTTP. To use it effectively, you need to understand not just what GraphQL is, but how it actually works under the hood.
1. Introduction
GraphQL is often marketed as the “REST killer,” but that’s not the full story. It’s a query language for APIs that gives clients the power to request exactly the data they need. Behind the hype, GraphQL is still just requests and responses over a transport protocol like HTTP. To use it effectively, you need to understand not just what GraphQL is, but how it actually works under the hood.
In this article, we’ll break down GraphQL transports (HTTP, WebSockets, even TCP/UDP), why queries are sent via POST, how browsers handle requests, caching challenges, rate limiting, security, and the cost of parsing. We’ll also use GitHub’s GraphQL API as a concrete example.
2. How GraphQL Moves Data
By default, GraphQL runs over HTTP POST. A client sends a JSON object containing three fields:
{
"query": "...",
"variables": { ... },
"operationName": "..."
}
Why POST?
GET requests can’t have a body. That means the query has to be encoded into the URL as a query parameter.
With nested queries, this quickly hits server/browser limits 2048 char in URL (e.g. a
414 URI Too Long
error).POST avoids this problem by letting you send the query, variables, and operation name inside the request body as JSON so you got an unlimited HTTP request .
Best practice: always use POST with application/json
. Some libraries support GET for persisted queries (where the client sends just a hash and the server looks up the query), but for general use, POST is the reliable, future proof choice.
3. Fetching the Schema
One of GraphQL’s superpowers is that it’s self describing. A client can send an introspection query to fetch the schema and understand what fields are available. Most servers expose this at /graphql
.
4. Example: Making a Request in the Browser
Here’s how you’d query GitHub’s GraphQL API using fetch
:
fetch("https://api.github.com/graphql", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
query: `
query($login: String!) {
user(login: $login) {
name
repositories(first: 3) {
nodes { name }
}
}
}
`,
variables: { login: "octocat" }
})
})
.then(res => res.json())
.then(console.log)
The response looks like this:
{
"data": {
"user": {
"name": "The Octocat",
"repositories": {
"nodes": [
{ "name": "Hello-World" },
{ "name": "Spoon-Knife" }
]
}
}
}
}
5. GraphQL Over TCP, UDP, and WebSockets
The GraphQL spec is transport-agnostic. HTTP is just the most common choice.
TCP: You can send GraphQL queries over raw TCP sockets. Rare in practice, but possible for microservice communication.
UDP: Not realistic: GraphQL requires ordered, reliable delivery.
WebSockets: Widely used for subscriptions (real-time events). Example: a subscription to get new messages in a chat app.
subscription {
newMessage {
id
content
sender { name }
}
}
Libraries like graphql-ws
or Apollo Subscriptions handle this in production.
6. Why Caching is Hard
With REST, GET /users/1
maps to a specific resource and can be cached easily.
With GraphQL, everything goes through a single endpoint like /graphql
. To cache, you’d need to parse the query and understand which fields it requests.
This means:
No
ETag
or straightforward HTTP caching out of the box.Clients (Apollo, Relay) handle caching themselves.
A workaround is persisted queries — send only a query hash to the server, which can then use normal caching.
7. Headers, Rate Limiting & REST-like Concerns
GraphQL still uses HTTP headers just like REST.
Authorization
for tokensContent-Type: application/json
Custom headers as needed
Rate limiting is more complex than REST:
REST: 1 request = 1 cost.
GraphQL: 1 request could be “light” or “heavy” depending on how much data it fetches.
GitHub solves this by assigning a point cost to queries. You get a quota of points per hour. Querying 100 repositories costs more than querying 1.
This means you need to design queries carefully:
Don’t request more fields than necessary.
Use pagination (
first
,after
) to fetch large data sets gradually.Cache results client-side when possible.
8. Complexity & Cost of Parsing
One of the trade-offs with GraphQL is that every request has to be parsed, validated, and executed — unlike REST, which usually just matches a URL to a handler.
Here’s what happens step by step:
-
Parse – The raw query string is converted into an AST (Abstract Syntax Tree).
- An AST is a structured tree representation of your query.
- Each field, argument, and nested selection becomes a node in that tree.
- Example:
{
user(login: "octocat") {
name
repositories(first: 2) {
nodes { name }
}
}
}
AST (simplified):
Operation: Query
└── Field: user (args: login="octocat")
├── Field: name
└── Field: repositories (args: first=2)
└── Field: nodes
└── Field: name
This structured tree makes it easier for the GraphQL engine to understand what’s being asked.
-
Validate – The server checks the AST against the schema:
- Does
user
exist as a type? - Does
repositories
belong touser
? - Are the arguments valid?
- Does
-
Execute – The server walks the AST node by node, calling the corresponding resolvers to fetch the data.
Why This Is Costly
Parsing and validation take CPU time compared to REST’s simple “route → handler” model.
Deep or malicious queries can blow up execution time (
friends { friends { friends ... }}
).Servers must defend themselves against expensive queries.
Mitigations
Query depth limiting – block queries that go too deep.
Query complexity analysis – assign a “cost score” to queries.
Persisted queries – skip parsing/validation for known queries by storing their AST on the server.
9. Security Concerns
GraphQL opens the door to:
DoS via expensive queries.
Data exposure if introspection is left on in production.
Injection attacks if resolvers are insecure.
Best practices:
Disable introspection in prod (unless you need it).
Add query depth/cost limits.
Sanitize inputs inside resolvers.
10. Error Handling in GraphQL
Error handling in GraphQL works differently than in REST. With REST, if something goes wrong you usually get an HTTP error code (400, 404, 500, etc.) and a message. In GraphQL, responses are always wrapped in JSON, and errors are returned in a dedicated errors field.
Example
If you send a query:
{
user(login: "does-not-exist") {
name
}
}
The response might look like:
{
"data": {
"user": null
},
"errors": [
{
"message": "Could not find user with login 'does-not-exist'",
"locations": [{ "line": 2, "column": 3 }],
"path": ["user"]
}
]
}
Notice a few things:
The data field is still present (with null for the invalid part).
The errors array contains messages, where they happened in the query (locations), and which path failed (path).
The HTTP status is still 200 OK because technically the request was valid, even though part of it failed.
Types of Errors
Validation errors – Query doesn’t match the schema (wrong field, wrong argument).
Execution errors – Resolver fails (e.g., database issue, missing resource).
Partial failures – Some fields resolve successfully, others fail. This is common and often useful — the client can still render partial data.
Best Practices
Don’t rely solely on HTTP codes. Expect errors in the response body.
Add error extensions – You can attach custom fields to errors (e.g., error codes, internal tracking IDs). Example:
{
"errors": [
{
"message": "Not authorized",
"extensions": { "code": "UNAUTHENTICATED" }
}
]
}
Monitor for abuse Since HTTP 200 is returned even on logical errors, API monitoring should inspect both data and errors.
Combine with headers For things like auth failures or rate limits, it’s common to still use HTTP headers (401 Unauthorized, 429 Too Many Requests) alongside the GraphQL error payload.
11. When to Use and When Not To
Use GraphQL when:
You have complex UIs that need nested data.
Mobile clients need efficient, custom responses.
You’re aggregating multiple backends into one schema.
Avoid GraphQL when:
You only need simple CRUD.
You rely heavily on CDN caching.
Your team isn’t ready for the added complexity.
12. Wrap-Up
GraphQL is powerful, but not a silver bullet. It trades simplicity and caching for flexibility and efficiency.
Over HTTP POST by default, but can run over WebSockets or TCP.
Harder to cache, harder to rate limit, more costly to parse.
Still uses headers, auth, and other REST-like patterns.
Great for complex data fetching, risky for simple APIs.
Think of GraphQL not as a replacement for REST, but as an option in your toolbox to be used when its strengths outweigh its complexity.
Top comments (1)
Usefull insights (: