GraphQL lets clients request exactly the data they need. This tutorial shows how to write GraphQL queries and mutations, using the Pet Store API as an example.
The GraphQL Endpoint
GraphQL uses a single endpoint for all operations. Send POST requests with your query:
curl -X POST "https://api.petstoreapi.com/v1/graphql" \
-H "Content-Type: application/json" \
-d '{
"query": "{ pets { name } }"
}'
All queries and mutations go through this one endpoint.
Writing Your First Query
A basic query fetches data. The structure matches your response:
query {
pets {
name
}
}
This returns all pets with their names:
{
"data": {
"pets": [
{ "name": "Fluffy" },
{ "name": "Buddy" },
{ "name": "Max" }
]
}
}
Add more fields:
query {
pets {
id
name
status
category {
name
}
}
}
The response includes exactly what you requested. No more, no less.
Query with Arguments
Filter results using arguments:
query {
pet(id: "123") {
name
status
category {
name
}
}
}
This fetches a single pet by ID. GraphQL validates that the pet exists and returns exactly those fields.
Filter with variables:
const query = `
query GetPet($petId: ID!) {
pet(id: $petId) {
name
status
}
}
`;
fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query,
variables: { petId: '123' }
})
});
Variables make queries reusable. Write one query, pass different IDs.
Filtering Results
GraphQL arguments filter collections:
query {
pets(status: AVAILABLE, limit: 10) {
id
name
category {
name
}
}
}
This returns only available pets, limited to 10 results.
Nested Queries
Fetch related data in one request:
query {
pet(id: "123") {
name
orders {
id
total
items {
quantity
product {
name
price
}
}
}
}
}
One query fetches the pet, its orders, order items, and product details. No N+1 requests.
Mutations: Changing Data
Mutations modify data. The syntax resembles queries but uses the mutation keyword:
mutation {
createPet(input: {
name: "Charlie"
status: AVAILABLE
categoryId: "1"
}) {
id
name
status
}
}
The response returns the created pet. You see the result of your change.
Updating data:
mutation {
updatePet(id: "123", input: {
name: "Charlie Jr."
status: SOLD
}) {
id
name
status
}
}
Deleting data:
mutation {
deletePet(id: "123")
}
The response is simply true or false for deletions.
Using Variables in Mutations
Pass input as variables:
const mutation = `
mutation CreatePet($input: CreatePetInput!) {
createPet(input: $input) {
id
name
}
}
`;
fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: mutation,
variables: {
input: {
name: "Charlie",
status: "AVAILABLE",
categoryId: "1"
}
}
})
});
Variables keep mutations clean and reusable.
Aliases
Aliases let you fetch the same field with different arguments:
query {
available: pets(status: AVAILABLE) {
name
}
sold: pets(status: SOLD) {
name
}
}
The response uses your alias names:
{
"data": {
"available": [{ "name": "Fluffy" }],
"sold": [{ "name": "Buddy" }]
}
}
Fragments
Fragments define reusable field sets:
fragment PetFields on Pet {
id
name
status
category {
name
}
}
query {
pet(id: "123") {
...PetFields
}
pets {
...PetFields
}
}
Define once, use everywhere. Fragments reduce repetition.
Error Handling
GraphQL returns errors in an errors array:
{
"data": { "pet": null },
"errors": [
{
"message": "Pet not found",
"locations": [{ "line": 2, "column": 3 }],
"path": ["pet"]
}
]
}
Check both data and errors in your response handler.
Real-World Example
Build a pet management interface:
// Fetch pet with orders
const GET_PET_WITH_ORDERS = `
query GetPetWithOrders($petId: ID!) {
pet(id: $petId) {
...PetBasicFields
category {
...CategoryFields
}
orders {
id
total
createdAt
}
}
}
fragment PetBasicFields on Pet {
id
name
status
}
fragment CategoryFields on Category {
id
name
}
`;
// Add a new pet
const ADD_PET = `
mutation AddPet($input: CreatePetInput!) {
createPet(input: $input) {
...PetBasicFields
}
}
fragment PetBasicFields on Pet {
id
name
status
}
`;
// Update pet status
const UPDATE_STATUS = `
mutation UpdateStatus($id: ID!, $status: PetStatus!) {
updatePet(id: $id, input: { status: $status }) {
id
name
status
}
}
`;
async function getPet(id) {
const result = await query(GET_PET_WITH_ORDERS, { petId: id });
return result.data.pet;
}
async function addPet(petData) {
const result = await query(ADD_PET, { input: petData });
return result.data.createPet;
}
async function updateStatus(id, status) {
const result = await query(UPDATE_STATUS, { id, status });
return result.data.updatePet;
}
This structure keeps queries organized and reusable.
Pet Store API GraphQL
The Pet Store API provides a complete GraphQL schema. Query pets, orders, customers, and inventory. Mutations handle CRUD operations.
Explore the schema at docs.petstoreapi.com/graphql. The interactive playground lets you test queries before implementing.
GraphQL gives your clients control over data shape. Fetch exactly what you need, no more, no less. The flexible query language adapts to any frontend requirement.