How GraphQL Uses JSON
JSON appears in three places:
| Where | Format | Example |
|---|---|---|
| Request body | {"query": "...", "variables": {...}} | Sent via HTTP POST |
| Response body | {"data": {...}, "errors": [...]} | Always returned as JSON |
| Introspection | {"__schema": {...}} | The schema itself as JSON |
The Request: Query + Variables as JSON
1{2 "query": "query GetUser($id: ID!) { user(id: $id) { name email posts { title } } }",3 "variables": {4 "id": "user_42"5 },6 "operationName": "GetUser"7}Note
query field is a GraphQL string (not JSON), but it is wrapped in a JSON object. The variables field is a JSON object that maps to the query's $ parameters. operationName is optional but useful when a document contains multiple operations.Variables — Type-Safe JSON
Variables are passed as JSON and must match the types declared in the query:
1{2 "query": "mutation CreatePost($input: PostInput!) { createPost(input: $input) { id title } }",3 "variables": {4 "input": {5 "title": "GraphQL & JSON Guide",6 "body": "Everything you need to know...",7 "tags": ["graphql", "json", "api"],8 "published": true9 }10 }11}The Response: Predictable JSON
Successful Response
The response JSON mirrors the exact shape of the query:
1{2 "data": {3 "user": {4 "name": "Alice Chen",5 "email": "[email protected]",6 "posts": [7 { "title": "Getting Started with GraphQL" },8 { "title": "JSON Best Practices" }9 ]10 }11 }12}Tip
user { name email }, you get exactly {"user": {"name": "...", "email": "..."}}. No extra fields, no missing fields. This predictability is a core advantage of GraphQL.Error Response
GraphQL errors include precise location and path information:
1{2 "data": {3 "user": {4 "name": "Alice Chen",5 "email": null6 }7 },8 "errors": [9 {10 "message": "Not authorized to access User.email",11 "locations": [{ "line": 3, "column": 5 }],12 "path": ["user", "email"],13 "extensions": {14 "code": "FORBIDDEN",15 "timestamp": "2026-04-02T14:30:00Z"16 }17 }18 ]19}Important
data and errors can coexist — a resolved field appears in data and a failed field appears as null in data with an entry in errors.GraphQL vs REST — JSON Response Comparison
REST: Multiple Endpoints, Fixed Shapes
1{2 "id": 42,3 "name": "Alice Chen",4 "email": "[email protected]",5 "bio": "Senior developer at...",6 "avatar_url": "https://...",7 "created_at": "2026-01-15T10:30:00Z",8 "followers_count": 1234,9 "following_count": 5610}The client gets all fields whether it needs them or not (over-fetching). To get user + posts, the client makes a second request to /api/users/42/posts.
GraphQL: One Endpoint, Custom Shape
1{2 "data": {3 "user": {4 "name": "Alice Chen",5 "posts": [6 { "title": "GraphQL Guide", "likes": 42 }7 ]8 }9 }10}The client requests exactly the fields it needs — no over-fetching, no second request.
| Feature | REST JSON | GraphQL JSON |
|---|---|---|
| Endpoints | Many (/users, /posts, /comments) | One (/graphql) |
| Response shape | Fixed by server | Defined by client query |
| Over-fetching | Common — returns all fields | Eliminated — only requested fields |
| Under-fetching | Common — needs multiple requests | Eliminated — nested in one query |
| Error format | HTTP status codes | 200 + errors array in body |
| Caching | HTTP caching (ETag, Cache-Control) | Requires client-side cache (Apollo) |
| File uploads | Native (multipart/form-data) | Needs apollo-upload-client or similar |
Fetching GraphQL with JavaScript
1async function graphqlFetch<T>(2 query: string,3 variables?: Record<string, unknown>4): Promise<T> {5 const response = await fetch('/graphql', {6 method: 'POST',7 headers: { 'Content-Type': 'application/json' },8 body: JSON.stringify({ query, variables }),9 });1011 const json = await response.json();1213 if (json.errors?.length) {14 const messages = json.errors.map((e: { message: string }) => e.message);15 throw new Error(`GraphQL errors: ${messages.join(', ')}`);16 }1718 return json.data as T;19}2021interface UserData {22 user: { name: string; email: string };23}2425const data = await graphqlFetch<UserData>(26 `query GetUser($id: ID!) { user(id: $id) { name email } }`,27 { id: "user_42" }28);29console.log(data.user.name); // "Alice Chen"Introspection — The Schema as JSON
GraphQL can describe its own schema via an introspection query. The response is JSON:
1{2 "data": {3 "__schema": {4 "queryType": { "name": "Query" },5 "types": [6 {7 "name": "User",8 "kind": "OBJECT",9 "fields": [10 { "name": "id", "type": { "name": "ID", "kind": "SCALAR" } },11 { "name": "name", "type": { "name": "String", "kind": "SCALAR" } },12 { "name": "posts", "type": { "name": null, "kind": "LIST" } }13 ]14 }15 ]16 }17 }18}Try It — Validate a GraphQL JSON Response
Try It Yourself
A valid GraphQL JSON response — try adding an errors array