Skip to content

GraphQL Reference

GraphQL Reference

GraphQL queries, mutations, subscriptions, fragments, variables, directives, schema definition, resolvers, and the N+1 problem — with real-world patterns for Apollo, urql, and server-side implementations.

Queries — reading data
# Basic query
query {
  user(id: "123") {
    id
    name
    email
  }
}

# Named query (always name your queries — required in production)
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
    avatar {
      url
      width
      height
    }
  }
}

# Variables (sent separately from the query)
{ "id": "123" }

# Nested queries
query GetUserWithPosts($userId: ID!, $postsLimit: Int = 10) {
  user(id: $userId) {
    id
    name
    posts(limit: $postsLimit, orderBy: CREATED_AT_DESC) {
      edges {
        node {
          id
          title
          publishedAt
          tags {
            name
          }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

# Aliases — fetch same field twice with different args
query ComparePrices {
  usdPrice: product(id: "42", currency: USD) {
    price
    currency
  }
  eurPrice: product(id: "42", currency: EUR) {
    price
    currency
  }
}
Mutations — writing data
# Basic mutation
mutation {
  createUser(input: { name: "Alice", email: "alice@example.com" }) {
    id
    name
    createdAt
  }
}

# Named mutation with variables (always use variables — never string-interpolate)
mutation CreateUser($input: CreateUserInput!) {
  createUser(input: $input) {
    id
    name
    email
    createdAt
  }
}
# Variables:
{ "input": { "name": "Alice", "email": "alice@example.com", "role": "ADMIN" } }

# Multiple mutations in one request (execute sequentially, unlike query fields)
mutation BatchUpdate($userId: ID!, $postId: ID!) {
  updateUser(id: $userId, input: { status: ACTIVE }) {
    id
    status
  }
  publishPost(id: $postId) {
    id
    publishedAt
  }
}

# Mutation with error handling (union return type pattern)
mutation DeleteUser($id: ID!) {
  deleteUser(id: $id) {
    ... on DeleteUserSuccess {
      deletedId
    }
    ... on UserNotFoundError {
      message
    }
    ... on UnauthorizedError {
      message
      requiredPermission
    }
  }
}
# This is better than throwing errors — the client can type-check responses
Fragments — reusable field sets
# Fragment definition
fragment UserFields on User {
  id
  name
  email
  avatar { url }
  createdAt
}

# Use fragment in query
query GetUsers {
  users {
    ...UserFields       # spread fragment
    role               # fields outside fragment
  }
}

# Inline fragment — conditional fields based on type (for interfaces/unions)
query GetTimeline {
  feed {
    id
    createdAt
    author { ...UserFields }
    ... on Post {
      title
      body
      comments { totalCount }
    }
    ... on Photo {
      url
      caption
      dimensions { width height }
    }
    ... on Video {
      url
      duration
      thumbnailUrl
    }
  }
}

# __typename — get the concrete type (needed for caching + inline fragments)
query GetFeedItem($id: ID!) {
  node(id: $id) {
    __typename
    id
    ... on Post { title }
    ... on Video { url duration }
  }
}
Schema definition language (SDL)
# Scalar types
scalar Date          # custom scalar (add coercion in resolver)
scalar Upload        # file uploads (apollo-upload-server)
scalar JSON          # arbitrary JSON blob

# Object type
type User {
  id:        ID!           # ! = non-null
  name:      String!
  email:     String!
  role:      UserRole!     # enum
  posts:     [Post!]!      # list of non-null Post, list itself non-null
  profile:   Profile       # nullable (omitted = null)
  createdAt: Date!
}

# Enum
enum UserRole {
  ADMIN
  EDITOR
  VIEWER
}

# Interface — shared fields
interface Node {
  id: ID!
}

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

# Union — one of several types (no shared fields)
union SearchResult = User | Post | Product

# Input type — for mutation arguments
input CreateUserInput {
  name:     String!
  email:    String!
  role:     UserRole = VIEWER   # default value
  sendWelcomeEmail: Boolean = true
}

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

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

type Subscription {
  userCreated: User!
  messageAdded(roomId: ID!): Message!
}
Directives — built-in and custom
# Built-in directives in queries
query GetProfile($showEmail: Boolean!, $preview: Boolean = false) {
  user(id: "123") {
    name
    email     @include(if: $showEmail)    # include field only if true
    bio       @skip(if: $preview)         # skip field if true
    avatar {
      url
      @deprecated(reason: "Use avatarUrl instead — remove after 2026-06-01")
    }
  }
}

# @defer — stream parts of the response as they resolve (experimental)
query GetUserWithSlowData {
  user(id: "123") {
    name
    email
    ... on User @defer {     # these fields sent in separate chunk
      followers { count }
      analytics { views }
    }
  }
}

# Schema directives (server-side)
# @deprecated — mark a field as deprecated
type User {
  login: String! @deprecated(reason: "Use 'username' field instead")
  username: String!
}

# Custom schema directive (e.g. @auth)
directive @auth(requires: UserRole = ADMIN) on FIELD_DEFINITION | OBJECT

type Mutation {
  deleteUser(id: ID!): Boolean! @auth(requires: ADMIN)
  createPost(input: CreatePostInput!): Post! @auth(requires: EDITOR)
}

# @cacheControl (Apollo Server)
type Product @cacheControl(maxAge: 3600) {
  id: ID!
  name: String!
  price: Float! @cacheControl(maxAge: 60)  # price refreshes more often
}
Resolvers and the N+1 problem
# Basic resolver (Node.js / Apollo Server)
const resolvers = {
  Query: {
    user: async (_, { id }, context) => {
      return context.db.users.findById(id);
    },
    users: async (_, { limit, offset }, context) => {
      return context.db.users.findAll({ limit, offset });
    },
  },

  Mutation: {
    createUser: async (_, { input }, context) => {
      if (!context.user) throw new GraphQLError("Unauthorized", {
        extensions: { code: "UNAUTHENTICATED" },
      });
      return context.db.users.create(input);
    },
  },

  User: {
    // Field resolver — called for each User in the response
    posts: async (parent, { limit }, context) => {
      return context.db.posts.findByAuthorId(parent.id, limit);
    },
  },
};

// N+1 problem:
// Query 100 users → 1 query for users + 100 queries for posts (one per user)
// Fix: DataLoader (batch + cache)
import DataLoader from "dataloader";

const postsByAuthorLoader = new DataLoader(async (authorIds) => {
  const posts = await db.posts.findByAuthorIds(authorIds);   // ONE query
  // Return in same order as authorIds (DataLoader requirement)
  return authorIds.map(id => posts.filter(p => p.authorId === id));
});

// In resolver:
User: {
  posts: (parent, _, context) =>
    context.loaders.postsByAuthor.load(parent.id),   // batched automatically
}

The N+1 problem is the most common GraphQL performance issue. Every field resolver that queries a database needs a DataLoader. Failing to do this makes GraphQL APIs slower than REST as data grows.

Apollo Client — queries, mutations, cache
# Apollo Client setup (React)
import { ApolloClient, InMemoryCache, ApolloProvider, gql, useQuery, useMutation } from "@apollo/client";

const client = new ApolloClient({
  uri: "/graphql",
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: { errorPolicy: "all" },
  },
});

# useQuery hook
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) { id name email }
  }
`;

function UserProfile({ userId }) {
  const { loading, error, data } = useQuery(GET_USER, {
    variables: { id: userId },
    skip: !userId,           # don't run if userId is falsy
    fetchPolicy: "cache-and-network",  # show cached + update in background
  });

  if (loading) return ;
  if (error) return ;
  return 
{data.user.name}
; } # useMutation hook const CREATE_USER = gql`mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name } }`; function CreateUserForm() { const [createUser, { loading, error }] = useMutation(CREATE_USER, { update(cache, { data: { createUser } }) { cache.modify({ fields: { users(existing = []) { const ref = cache.writeFragment({ data: createUser, fragment: gql`fragment NewUser on User { id name }`, }); return [...existing, ref]; }, }, }); }, }); return (
{ e.preventDefault(); createUser({ variables: { input: { name: e.target.name.value } } }); }}>...
); }

🔍 Free tool: npm Package Health Checker — check graphql-js, Apollo Client, and other GraphQL packages for known CVEs and maintenance status.

Founded

2023 in London, UK

Contact

hello@releaserun.com