Query & Mutation Patterns
Advanced patterns for GraphQL queries, mutations, and complex operations.
Advanced Query Patterns
Nested Queries with Variables
query GetUserWithPosts(
$userId: ID!
$postLimit: Int = 5
$includeComments: Boolean = false
) {
user(id: $userId) {
id
name
email
posts(first: $postLimit) {
edges {
node {
id
title
content
publishedAt
comments(first: 3) @include(if: $includeComments) {
edges {
node {
id
content
author {
name
}
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
Fragment Composition
# Base fragments
fragment UserBasic on User {
id
name
email
}
fragment UserProfile on User {
...UserBasic
bio
avatar
location
website
}
fragment PostBasic on Post {
id
title
content
publishedAt
}
fragment PostWithAuthor on Post {
...PostBasic
author {
...UserBasic
}
}
fragment PostDetailed on Post {
...PostWithAuthor
tags {
id
name
}
commentCount
likeCount
}
# Complex query using fragments
query GetPostFeed($first: Int!, $after: String) {
posts(first: $first, after: $after) {
edges {
node {
...PostDetailed
comments(first: 3) {
edges {
node {
id
content
author {
...UserBasic
}
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
Conditional Queries
query GetContent($type: ContentType!, $id: ID!) {
content(type: $type, id: $id) {
... on Post {
id
title
content
author {
name
}
tags {
name
}
}
... on Article {
id
headline
body
author {
name
}
category {
name
}
}
... on Video {
id
title
description
duration
thumbnailUrl
creator {
name
}
}
}
}
Batch Queries
query BatchUserData($userIds: [ID!]!) {
users(ids: $userIds) {
id
name
email
postCount
followerCount
}
# Multiple different queries in one request
popularPosts: posts(filter: { popular: true }, first: 10) {
edges {
node {
id
title
likeCount
}
}
}
trendingTags: tags(trending: true, first: 20) {
id
name
postCount
}
}
Mutation Patterns
Basic CRUD Mutations
# Create mutations
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
success
post {
id
title
content
author {
name
}
}
errors {
field
message
}
}
}
# Update mutations
mutation UpdatePost($id: ID!, $input: UpdatePostInput!) {
updatePost(id: $id, input: $input) {
success
post {
id
title
content
updatedAt
}
errors {
field
message
}
}
}
# Delete mutations
mutation DeletePost($id: ID!) {
deletePost(id: $id) {
success
deletedId
message
}
}
Batch Mutations
# Batch create
mutation CreateMultiplePosts($inputs: [CreatePostInput!]!) {
createPosts(inputs: $inputs) {
success
posts {
id
title
}
errors {
index
field
message
}
}
}
# Batch update
mutation UpdateMultiplePosts($updates: [UpdatePostBatchInput!]!) {
updatePosts(updates: $updates) {
success
posts {
id
title
updatedAt
}
errors {
id
field
message
}
}
}
input UpdatePostBatchInput {
id: ID!
input: UpdatePostInput!
}
# Bulk operations
mutation BulkUpdatePosts($filter: PostFilter!, $update: BulkUpdateInput!) {
bulkUpdatePosts(filter: $filter, update: $update) {
success
affectedCount
errors {
message
}
}
}
input BulkUpdateInput {
status: PostStatus
tags: UpdateTagsInput
publishedAt: DateTime
}
Transaction-like Mutations
mutation CreatePostWithTags(
$postInput: CreatePostInput!
$tagInputs: [CreateTagInput!]!
) {
createPostWithTags(postInput: $postInput, tagInputs: $tagInputs) {
success
post {
id
title
tags {
id
name
}
}
createdTags {
id
name
}
errors {
field
message
}
}
}
# Complex business operation
mutation PublishPostWithNotifications(
$postId: ID!
$notificationSettings: NotificationInput!
) {
publishPost(postId: $postId, notificationSettings: $notificationSettings) {
success
post {
id
status
publishedAt
}
notifications {
id
type
recipient {
name
}
}
errors {
field
message
}
}
}
Optimistic Mutations
# Client-side optimistic update
mutation LikePost($postId: ID!) {
likePost(postId: $postId) {
success
post {
id
likeCount
liked # Whether current user has liked
}
like {
id
createdAt
}
}
}
# With version for concurrency control
mutation UpdatePostOptimistic(
$id: ID!
$version: Int!
$input: UpdatePostInput!
) {
updatePost(id: $id, version: $version, input: $input) {
success
post {
id
title
content
version # Incremented version
updatedAt
}
errors {
field
message
code # CONFLICT_ERROR for version mismatch
}
}
}
Complex Query Patterns
Aggregation Queries
query GetPostStatistics($filter: PostFilter) {
postStatistics(filter: $filter) {
totalCount
publishedCount
draftCount
# Grouping
byStatus {
status
count
}
byAuthor {
author {
id
name
}
count
}
byMonth {
month
year
count
}
# Metrics
averageWordCount
totalViews
totalLikes
totalComments
# Top performers
mostLiked(limit: 5) {
id
title
likeCount
}
mostCommented(limit: 5) {
id
title
commentCount
}
}
}
Search and Filtering
query SearchContent(
$query: String!
$filters: SearchFilter!
$sort: SearchSort
) {
search(query: $query, filters: $filters, sort: $sort) {
totalCount
facets {
type {
value
count
}
author {
value
count
}
tags {
value
count
}
publishedYear {
value
count
}
}
results {
... on Post {
id
title
content
author {
name
}
relevanceScore
highlights {
field
snippet
}
}
... on User {
id
name
bio
relevanceScore
highlights {
field
snippet
}
}
}
suggestions {
text
count
}
}
}
input SearchFilter {
types: [ContentType!]
authors: [ID!]
tags: [String!]
dateRange: DateRangeInput
status: [PostStatus!]
}
input SearchSort {
field: SearchSortField!
direction: SortDirection!
}
enum SearchSortField {
RELEVANCE
DATE
POPULARITY
TITLE
}
Recursive Queries
# Comment thread with nested replies
query GetCommentThread($postId: ID!, $depth: Int = 3) {
post(id: $postId) {
id
title
comments {
...CommentWithReplies
}
}
}
fragment CommentWithReplies on Comment {
id
content
author {
name
avatar
}
createdAt
likeCount
replies(first: 10) @depth(max: $depth) {
...CommentWithReplies
}
}
# Category hierarchy
query GetCategoryTree($rootId: ID) {
categories(parentId: $rootId) {
...CategoryTree
}
}
fragment CategoryTree on Category {
id
name
description
postCount
children {
...CategoryTree
}
}
Real-time Patterns
Live Queries
# Live query with updates
query LivePostStats($postId: ID!) @live {
post(id: $postId) {
id
title
likeCount
commentCount
viewCount
# Real-time activity
activeViewers
recentComments(first: 5) {
id
content
author {
name
}
createdAt
}
}
}
Subscription Queries
# Subscribe to new comments
subscription OnNewComment($postId: ID!) {
commentAdded(postId: $postId) {
id
content
author {
name
avatar
}
createdAt
# Include parent post info
post {
id
title
}
}
}
# Subscribe to user activity
subscription OnUserActivity($userId: ID!) {
userActivity(userId: $userId) {
... on PostCreated {
post {
id
title
}
timestamp
}
... on CommentAdded {
comment {
id
content
post {
title
}
}
timestamp
}
... on PostLiked {
post {
id
title
}
timestamp
}
}
}
Client-Side Patterns
Query Composition
// Apollo Client query composition
import { gql, useQuery } from '@apollo/client';
const GET_USER_POSTS = gql`
query GetUserPosts($userId: ID!, $first: Int!) {
user(id: $userId) {
id
name
posts(first: $first) {
edges {
node {
id
title
publishedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`;
function UserPosts({ userId }) {
const { data, loading, error, fetchMore } = useQuery(GET_USER_POSTS, {
variables: { userId, first: 10 },
notifyOnNetworkStatusChange: true,
});
const loadMore = () => {
fetchMore({
variables: {
first: 10,
after: data.user.posts.pageInfo.endCursor,
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return {
user: {
...prev.user,
posts: {
...fetchMoreResult.user.posts,
edges: [
...prev.user.posts.edges,
...fetchMoreResult.user.posts.edges,
],
},
},
};
},
});
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{data.user.posts.edges.map(({ node }) => (
<div key={node.id}>
<h3>{node.title}</h3>
<p>{node.publishedAt}</p>
</div>
))}
{data.user.posts.pageInfo.hasNextPage && (
<button onClick={loadMore}>Load More</button>
)}
</div>
);
}
Mutation with Cache Update
const CREATE_POST = gql`
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
success
post {
id
title
content
author {
id
name
}
createdAt
}
errors {
field
message
}
}
}
`;
const [createPost] = useMutation(CREATE_POST, {
update(cache, { data }) {
if (data.createPost.success) {
// Update user's posts cache
const userId = data.createPost.post.author.id;
const existingPosts = cache.readQuery({
query: GET_USER_POSTS,
variables: { userId, first: 10 },
});
cache.writeQuery({
query: GET_USER_POSTS,
variables: { userId, first: 10 },
data: {
user: {
...existingPosts.user,
posts: {
...existingPosts.user.posts,
edges: [
{ node: data.createPost.post, __typename: 'PostEdge' },
...existingPosts.user.posts.edges,
],
},
},
},
});
}
},
optimisticResponse: {
createPost: {
success: true,
post: {
id: 'temp-id',
title: variables.input.title,
content: variables.input.content,
author: {
id: currentUserId,
name: currentUserName,
},
createdAt: new Date().toISOString(),
},
errors: [],
},
},
});
Error Handling Patterns
// Global error handling
const errorLink = onError(
({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path, extensions }) => {
console.error(
`GraphQL error: Message: ${message}, Location: ${locations}, Path: ${path}`
);
// Handle specific error types
if (extensions?.code === 'UNAUTHENTICATED') {
// Redirect to login
window.location.href = '/login';
} else if (extensions?.code === 'VALIDATION_ERROR') {
// Show validation error
showValidationError(extensions.field, message);
}
});
}
if (networkError) {
console.error(`Network error: ${networkError}`);
showNetworkError();
}
}
);
// Query-specific error handling
const { data, loading, error } = useQuery(GET_POSTS, {
errorPolicy: 'all', // Return both data and errors
onError: error => {
// Handle query-specific errors
if (error.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN')) {
showAccessDeniedMessage();
}
},
});
This comprehensive guide covers advanced query and mutation patterns for building sophisticated GraphQL applications with complex data requirements and real-time features.