GraphQL has rapidly become one of the most popular API query languages, giving clients the power to ask for exactly what they need — nothing more, nothing less. If you're coming from a REST background, the mental shift can feel steep at first, but once it clicks, you won't want to go back.
What is a GraphQL Query?
A query is how you fetch data in GraphQL. Unlike REST where different endpoints return fixed shapes of data, a query lets you describe the exact shape of the response you want. The server then resolves only the fields you requested.
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
title
publishedAt
}
}
}Tip: Always name your operations (e.g.
GetUser) — it makes debugging in Apollo Studio and server logs dramatically easier.
What is a Mutation?
While queries are read-only, mutations are how you write data. They follow the same syntax but signal to the server that a side-effect is intended — creating, updating, or deleting records.
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
createdAt
}
}Variables
Hard-coding values into a query string is an antipattern. Variables keep queries clean and reusable. Pass them as a separate JSON object alongside the query.
{
"input": {
"title": "Hello GraphQL",
"body": "This is my first GraphQL-powered post.",
"authorId": "usr_abc123"
}
}Sending Requests from the Client
GraphQL runs over a single HTTP endpoint (usually /graphql) using POST. You can use any HTTP client, but libraries like Apollo Client and urql add caching, reactive updates, and error handling out of the box.
// Using fetch directly
const response = await fetch("/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `
query GetUser($id: ID!) {
user(id: $id) { id name email }
}
`,
variables: { id: "usr_abc123" },
}),
})
const { data, errors } = await response.json()Warning: Always check for the
errorsarray in the response. GraphQL can return a 200 status with partial data and errors simultaneously.
Key Takeaways
- Queries fetch data; mutations write it — both use the same syntax
- Always use named operations and variables
- GraphQL runs over a single POST endpoint
- Client libraries (Apollo, urql) add caching and reactive updates
- Always handle the
errorsarray even on HTTP 200 responses
This is just the beginning. Once you're comfortable with queries and mutations, explore subscriptions for real-time data, fragments for reusable field sets, and directives for conditional field inclusion.