GraphQL is a query language for APIs. Instead of calling many fixed endpoints that each return a predefined shape, a client sends one query to a single endpoint and describes exactly the fields it wants. The server returns JSON in that same shape. This guide covers the core concepts: queries, arguments, variables, fragments, mutations and errors.
Given a schema with a user type, this query asks for a user's name and the titles of their posts:
graphqlquery { user(id: "123") { name posts { title } } }
The response mirrors the query:
json{ "data": { "user": { "name": "Asha", "posts": [ { "title": "Hello GraphQL" }, { "title": "Second post" } ] } } }
You get exactly the fields you asked for, nothing more, and related data comes back in one request.
Every GraphQL API is defined by a schema of types. The Query type lists entry points for reading, and Mutation lists entry points for writing:
graphqltype User { id: ID! name: String! email: String posts: [Post!]! } type Query { user(id: ID!): User }
! means non-null, and [Post!]! means a non-null list of non-null posts. Because the schema is typed, the server rejects invalid queries before running them, and tools can offer autocomplete.
Don't build queries by concatenating strings. Declare variables with types and pass the values separately:
graphqlquery GetUser($id: ID!) { user(id: $id) { name } }
json{ "id": "123" }
Named operations (GetUser) also make server logs and monitoring much easier to read.
Aliases let you request the same field twice with different arguments:
graphqlquery { me: user(id: "123") { name } friend: user(id: "456") { name } }
Fragments let you reuse a set of fields:
graphqlfragment UserCard on User { id name } query { user(id: "123") { ...UserCard } }
Mutations change data and return the updated result, so the client can refresh its view without a second request:
graphqlmutation CreatePost($input: CreatePostInput!) { createPost(input: $input) { id title } }
json{ "input": { "title": "My First Post", "body": "Hello world!" } }
GraphQL usually returns HTTP 200 even when something goes wrong, and reports problems in an errors array next to any partial data:
json{ "data": { "user": null }, "errors": [ { "message": "User not found", "path": ["user"], "extensions": { "code": "NOT_FOUND" } } ] }
Always check errors, even on a 200 response. A missing errors array is the only reliable sign of full success.
A GraphQL server can describe its own schema through introspection queries. That's what powers autocomplete, schema explorers and generated documentation. Some production APIs disable introspection for security; in that case, you need the schema file from the API's maintainers.
The GraphQL Client lets you send queries and mutations to any public endpoint, with variables and headers, over HTTP or WebSocket. It loads the schema by introspection, or from a schema file you import, so you can browse types while writing queries. See the GraphQL client documentation for details, and GraphQL vs REST for when to choose each approach.