Toolmingo
Guides9 min read

GraphQL Cheat Sheet: Queries, Mutations, and Schema

A complete GraphQL cheat sheet — schema definition, queries, mutations, subscriptions, fragments, directives, variables, Apollo Client, and server setup examples.

GraphQL lets you describe exactly what data you want and get back exactly that — no over-fetching, no under-fetching. This reference covers every concept from schema definition to client queries in one place.

Quick reference

Operation Syntax What it does
Query query { user { name } } Fetch data (read)
Mutation mutation { createUser(input: {…}) { id } } Write/change data
Subscription subscription { messageAdded { text } } Real-time push
Fragment fragment F on User { id name } Reusable field set
Variable query($id: ID!) { user(id: $id) { name } } Parameterised operation
Alias me: user(id: 1) { name } Rename response field
Inline fragment ... on Admin { adminLevel } Conditional fields on union
Directive @include field @include(if: $show) Conditionally include field
Directive @skip field @skip(if: $hide) Conditionally skip field
Introspection { __schema { types { name } } } Inspect the schema

Schema definition language (SDL)

Scalar types

Scalar Description
Int 32-bit signed integer
Float Double-precision float
String UTF-8 character sequence
Boolean true or false
ID Unique identifier (serialised as String)

Custom scalars: scalar Date, scalar JSON, scalar Upload

Type definitions

# Object type
type User {
  id: ID!            # ! = non-null
  name: String!
  email: String!
  age: Int           # nullable
  role: Role!        # enum
  posts: [Post!]!    # non-null list of non-null items
  createdAt: String!
}

# Enum
enum Role {
  ADMIN
  EDITOR
  VIEWER
}

# Input type (for mutations / arguments)
input CreateUserInput {
  name: String!
  email: String!
  role: Role = VIEWER   # default value
}

# Interface
interface Node {
  id: ID!
}

type Post implements Node {
  id: ID!
  title: String!
  body: String!
  author: User!
  tags: [String!]!
  publishedAt: String
}

# Union type
union SearchResult = User | Post | Comment

# Root types
type Query {
  user(id: ID!): User
  users(limit: Int = 10, offset: Int = 0): [User!]!
  search(query: String!): [SearchResult!]!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
}

type Subscription {
  userCreated: User!
  postPublished(authorId: ID): Post!
}

Queries

Basic query

query {
  users {
    id
    name
    email
  }
}

Query with arguments

query {
  user(id: "42") {
    name
    email
    posts {
      title
      publishedAt
    }
  }
}

Named query (best practice for production)

query GetUser {
  user(id: "42") {
    id
    name
    email
  }
}

Query with variables

Always prefer variables over string interpolation — they're typed and injection-safe.

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    role
  }
}

Variables JSON:

{ "id": "42" }

Aliases — fetch the same field twice

query {
  admin: user(id: "1") {
    name
    email
  }
  editor: user(id: "2") {
    name
    email
  }
}

Nested queries

query {
  users {
    id
    name
    posts {
      id
      title
      tags
      author {
        name
      }
    }
  }
}

Fragments

Fragments let you extract a set of fields and reuse them across multiple queries.

fragment UserFields on User {
  id
  name
  email
  role
}

query {
  user(id: "1") {
    ...UserFields
    posts {
      title
    }
  }
  me: user(id: "99") {
    ...UserFields
  }
}

Inline fragments — union types

query {
  search(query: "graphql") {
    ... on User {
      name
      email
    }
    ... on Post {
      title
      publishedAt
    }
    ... on Comment {
      body
      author {
        name
      }
    }
  }
}

Directives

@include and @skip

query GetUser($id: ID!, $withPosts: Boolean!, $skipEmail: Boolean!) {
  user(id: $id) {
    id
    name
    email @skip(if: $skipEmail)
    posts @include(if: $withPosts) {
      title
    }
  }
}

Variables:

{ "id": "1", "withPosts": true, "skipEmail": false }

@deprecated (schema directive)

type User {
  id: ID!
  name: String!
  username: String @deprecated(reason: "Use name instead")
}

Mutations

Create

mutation CreateUser($input: CreateUserInput!) {
  createUser(input: $input) {
    id
    name
    email
    role
  }
}

Variables:

{
  "input": {
    "name": "Ana Petrović",
    "email": "ana@example.com",
    "role": "EDITOR"
  }
}

Update

mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
  updateUser(id: $id, input: $input) {
    id
    name
    email
  }
}

Delete

mutation DeleteUser($id: ID!) {
  deleteUser(id: $id)
}

Multiple mutations in sequence

Mutations in a single request run in order (unlike queries, which run in parallel).

mutation {
  createCategory(name: "Tech") {
    id
  }
  createPost(title: "Hello World", categoryId: "…") {
    id
    title
  }
}

Subscriptions

subscription {
  userCreated {
    id
    name
    email
  }
}

With variable:

subscription OnNewPost($authorId: ID!) {
  postPublished(authorId: $authorId) {
    id
    title
    author {
      name
    }
  }
}

Introspection

Introspection lets you query the schema itself.

# All types
{
  __schema {
    types {
      name
      kind
    }
  }
}

# All fields on a type
{
  __type(name: "User") {
    fields {
      name
      type {
        name
        kind
        ofType { name kind }
      }
    }
  }
}

# Available queries
{
  __schema {
    queryType {
      fields {
        name
        description
      }
    }
  }
}

Server setup

Node.js — Apollo Server 4

npm install @apollo/server graphql
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const typeDefs = `#graphql
  type User {
    id: ID!
    name: String!
    email: String!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
  }

  type Mutation {
    createUser(name: String!, email: String!): User!
  }
`;

const users = [{ id: '1', name: 'Ana', email: 'ana@example.com' }];

const resolvers = {
  Query: {
    users: () => users,
    user: (_, { id }) => users.find(u => u.id === id) ?? null,
  },
  Mutation: {
    createUser: (_, { name, email }) => {
      const user = { id: String(users.length + 1), name, email };
      users.push(user);
      return user;
    },
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`Server ready at ${url}`);

Node.js — Next.js App Router route handler

// app/api/graphql/route.ts
import { ApolloServer } from '@apollo/server';
import { startServerAndCreateNextHandler } from '@as-integrations/next';

const server = new ApolloServer({ typeDefs, resolvers });
const handler = startServerAndCreateNextHandler(server);

export { handler as GET, handler as POST };

Python — Strawberry

pip install strawberry-graphql[fastapi] uvicorn
import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

@strawberry.type
class User:
    id: str
    name: str
    email: str

users_db = [User(id="1", name="Ana", email="ana@example.com")]

@strawberry.type
class Query:
    @strawberry.field
    def users(self) -> list[User]:
        return users_db

    @strawberry.field
    def user(self, id: str) -> User | None:
        return next((u for u in users_db if u.id == id), None)

schema = strawberry.Schema(query=Query)
graphql_app = GraphQLRouter(schema)

app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")

Go — gqlgen

go get github.com/99designs/gqlgen
go run github.com/99designs/gqlgen init

After running go generate ./... from your schema.graphqls:

// resolver.go
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
    return r.DB.FindAllUsers(ctx)
}

func (r *mutationResolver) CreateUser(
    ctx context.Context, input model.CreateUserInput,
) (*model.User, error) {
    return r.DB.CreateUser(ctx, input)
}

Client — fetch API (no library)

async function gql(query, variables = {}) {
  const res = await fetch('/api/graphql', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, variables }),
  });
  const { data, errors } = await res.json();
  if (errors?.length) throw new Error(errors[0].message);
  return data;
}

// Usage
const data = await gql(
  `query GetUser($id: ID!) { user(id: $id) { name email } }`,
  { id: '1' }
);
console.log(data.user.name);

Client — Apollo Client (React)

npm install @apollo/client graphql
// app/providers.tsx
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

const client = new ApolloClient({
  uri: '/api/graphql',
  cache: new InMemoryCache(),
});

export function Providers({ children }: { children: React.ReactNode }) {
  return <ApolloProvider client={client}>{children}</ApolloProvider>;
}

useQuery

import { useQuery, gql } from '@apollo/client';

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;

function UserList() {
  const { data, loading, error } = useQuery(GET_USERS);

  if (loading) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.users.map((user) => (
        <li key={user.id}>{user.name} — {user.email}</li>
      ))}
    </ul>
  );
}

useMutation

import { useMutation, gql } from '@apollo/client';

const CREATE_USER = gql`
  mutation CreateUser($name: String!, $email: String!) {
    createUser(name: $name, email: $email) {
      id
      name
    }
  }
`;

function CreateUserForm() {
  const [createUser, { loading, error }] = useMutation(CREATE_USER, {
    refetchQueries: ['GetUsers'],  // refresh the list after create
  });

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = new FormData(e.currentTarget);
    createUser({ variables: { name: form.get('name'), email: form.get('email') } });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <button disabled={loading}>Create</button>
      {error && <p>{error.message}</p>}
    </form>
  );
}

useSubscription

import { useSubscription, gql } from '@apollo/client';

const USER_CREATED = gql`
  subscription {
    userCreated {
      id
      name
    }
  }
`;

function LiveFeed() {
  const { data } = useSubscription(USER_CREATED);
  return <p>Latest: {data?.userCreated.name}</p>;
}

Pagination patterns

Offset-based (simple)

query {
  users(limit: 10, offset: 20) {
    id
    name
  }
}

Cursor-based / Relay connection (recommended for large datasets)

query {
  usersConnection(first: 10, after: "cursor==") {
    edges {
      cursor
      node {
        id
        name
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Common mistakes

Mistake Problem Fix
Fetching __typename manually everywhere Noise in queries Apollo caches it automatically — only add when needed for union disambiguation
N+1 queries in resolvers Each user triggers a DB call per post Use DataLoader to batch and cache per request
Returning null instead of throwing Client can't tell if "no data" or "error" Throw a descriptive error from the resolver
Skipping named operations Hard to debug in logs and Apollo Studio Always name your queries/mutations
Putting business logic in resolvers Hard to test, hard to reuse Move logic to a service layer, resolvers just delegate
@deprecated on input fields Not supported in SDL Document in description instead; remove after migration
Exposing introspection in production Schema leaks sensitive structure Disable introspection on public endpoints

FAQ

What's the difference between a query and a mutation? Semantically: queries are read-only, mutations cause side effects. Technically: queries run in parallel, mutations run in series within a single request.

Can I send multiple queries in one request? Yes — that's called query batching. Send an array of { query, variables } objects. Apollo Client does this automatically when you configure BatchHttpLink.

What is N+1 and how do I fix it? When a list query triggers one DB call per item (e.g., fetching 100 users and then 100 separate queries for each user's posts). Fix with DataLoader: it batches all IDs collected during a request tick into a single query.

GraphQL vs REST — when to choose which? Use GraphQL when: clients need flexible queries, you have multiple clients with different data needs, or you're building a BFF (backend-for-frontend). Use REST when: you have a simple CRUD API, caching by URL matters (CDN), or your team is unfamiliar with GraphQL.

How do I handle authentication? Pass a JWT in the Authorization: Bearer <token> header. In Apollo Server, extract it in the context function and attach the decoded user. Resolvers then access context.user to check permissions.

What is schema stitching vs federation? Schema stitching merges multiple schemas in the gateway layer (older approach, more manual). Apollo Federation is the modern approach — each service owns its schema slice and annotates it with @key directives; the router assembles the supergraph automatically.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools