Skip to main content

GraphQL Cheatsheet

Comprehensive quick reference for GraphQL syntax, schema definition, queries, mutations, subscriptions, and best practices. Essential guide for developers working with GraphQL.

Table of Contents


Prerequisites

GraphQL Version: This cheatsheet targets GraphQL.js 16.0+ and Apollo Server 4.0+. Some features require specific versions (noted inline).

Terminal window
# Install GraphQL core library
pnpm add graphql
# Install Apollo Server (Express)
pnpm add @apollo/server express
pnpm add -D @types/express
# Install Apollo Client (React)
pnpm add @apollo/client
# Install GraphQL Code Generator (optional)
pnpm add -D @graphql-codegen/cli @graphql-codegen/typescript

Node.js: GraphQL.js 16.0+ requires Node.js 14.17+ or Node.js 16+.


Schema Definition

Type Definitions 🔧

# Scalar types (built-in)
type User {
id: ID! # Non-nullable ID
name: String! # Non-nullable String
email: String # Nullable String
age: Int # Nullable Int
score: Float # Nullable Float
isActive: Boolean! # Non-nullable Boolean
createdAt: String # Custom scalar (ISO date string)
}
# Enum types
enum UserRole {
ADMIN
USER
GUEST
}
type User {
role: UserRole! # Must be one of: ADMIN, USER, GUEST
}
# List types
type Post {
id: ID!
title: String!
tags: [String!]! # Non-null list of non-null strings
comments: [Comment] # Nullable list of nullable comments
authors: [User!]! # Non-null list of non-null users
}
# Union types
union SearchResult = User | Post | Comment
type Query {
search(term: String!): [SearchResult!]!
}
# Interface types
interface Node {
id: ID!
createdAt: String!
}
type User implements Node {
id: ID!
name: String!
createdAt: String!
}
type Post implements Node {
id: ID!
title: String!
createdAt: String!
}

Input Types 📥

# Input types for mutations
input CreateUserInput {
name: String!
email: String!
age: Int
role: UserRole = USER # Default value
}
input UpdateUserInput {
id: ID!
name: String
email: String
age: Int
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(input: UpdateUserInput!): User!
}

Root Types 🌳

# Query type (read operations)
type Query {
# Simple query
user(id: ID!): User
# Query with list return
users(limit: Int = 10, offset: Int = 0): [User!]!
# Query with filters
posts(filter: PostFilter, sort: SortOrder): [Post!]!
# Query with multiple arguments
search(query: String!, type: SearchType): [SearchResult!]!
}
# Mutation type (write operations)
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
# Batch operations
createUsers(input: [CreateUserInput!]!): [User!]!
}
# Subscription type (real-time updates)
type Subscription {
userCreated: User!
userUpdated(id: ID!): User!
postLiked(postId: ID!): Post!
}

Schema File Example 📄

schema.graphql
scalar DateTime
scalar JSON
type Query {
me: User
user(id: ID!): User
users: [User!]!
}
type Mutation {
login(email: String!, password: String!): AuthPayload!
createUser(input: CreateUserInput!): User!
}
type Subscription {
messageAdded(roomId: ID!): Message!
}
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
}
input CreateUserInput {
name: String!
email: String!
password: String!
}

Queries

Basic Query 🔍

# Query single field
query {
user {
name
}
}
# Query multiple fields
query {
user {
id
name
email
}
}
# Query with arguments
query {
user(id: "123") {
id
name
email
}
}
# Query nested fields
query {
user(id: "123") {
id
name
posts {
id
title
comments {
id
content
}
}
}
}

Query with Aliases 🏷️

# Use aliases to query same field multiple times
query {
firstUser: user(id: "1") {
name
}
secondUser: user(id: "2") {
name
}
}
# Response:
# {
# "data": {
# "firstUser": { "name": "Alice" },
# "secondUser": { "name": "Bob" }
# }
# }

Query with Fragments 📋

# Define fragment
fragment UserFields on User {
id
name
email
}
# Use fragment in query
query {
user(id: "123") {
...UserFields
posts {
title
}
}
}
# Multiple fragments
fragment UserBasic on User {
id
name
}
fragment UserDetails on User {
email
createdAt
}
query {
user(id: "123") {
...UserBasic
...UserDetails
}
}

Query with Variables 🔄

# Query definition with variables
query GetUser($userId: ID!, $includePosts: Boolean!) {
user(id: $userId) {
id
name
posts @include(if: $includePosts) {
title
}
}
}
# Variables JSON
{
"userId": "123",
"includePosts": true
}

Query with Directives 🎯

# @include directive (include field if condition is true)
query {
user(id: "123") {
id
name
email @include(if: $includeEmail)
}
}
# @skip directive (skip field if condition is true)
query {
user(id: "123") {
id
name
email @skip(if: $hideEmail)
}
}
# @deprecated directive (in schema)
type User {
id: ID!
name: String!
oldField: String @deprecated(reason: "Use newField instead")
newField: String!
}

Mutations

Basic Mutation ✏️

# Simple mutation
mutation {
createUser(input: {
name: "Alice"
email: "alice@example.com"
}) {
id
name
email
}
}
# Mutation with variables
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
createdAt
}
}
# Variables
{
"input": {
"name": "Alice",
"email": "alice@example.com",
"age": 30
}
}

Multiple Mutations 🔄

# Execute multiple mutations in one request
mutation {
createUser(input: { name: "Alice", email: "alice@example.com" }) {
id
name
}
createPost(input: { title: "Hello", content: "World" }) {
id
title
}
}
# Note: Mutations execute sequentially, not in parallel

Mutation with Error Handling ⚠️

# Mutation that may fail
mutation {
updateUser(id: "123", input: { name: "Bob" }) {
id
name
}
}
# Response on error:
# {
# "errors": [
# {
# "message": "User not found",
# "path": ["updateUser"],
# "extensions": {
# "code": "USER_NOT_FOUND"
# }
# }
# ],
# "data": null
# }

Optimistic Updates 🚀

# Client-side optimistic update pattern
mutation UpdatePost($id: ID!, $title: String!) {
updatePost(id: $id, input: { title: $title }) {
id
title
updatedAt
}
}
# Apollo Client example:
# const [updatePost] = useMutation(UPDATE_POST, {
# optimisticResponse: {
# updatePost: {
# id: $id,
# title: $title,
# updatedAt: new Date().toISOString()
# }
# }
# });

Subscriptions

Basic Subscription 📡

# Subscribe to real-time updates
subscription {
userCreated {
id
name
email
}
}
# Subscription with arguments
subscription {
messageAdded(roomId: "123") {
id
content
author {
name
}
createdAt
}
}
# Subscription with variables
subscription OnMessageAdded($roomId: ID!) {
messageAdded(roomId: $roomId) {
id
content
author {
name
}
}
}

WebSocket Connection 🔌

// Apollo Client WebSocket setup
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const wsLink = new GraphQLWsLink(
createClient({
url: "ws://localhost:4000/graphql",
connectionParams: {
authToken: "your-auth-token",
},
}),
);
// Split link (HTTP for queries/mutations, WS for subscriptions)
import { split, HttpLink } from "@apollo/client";
import { getMainDefinition } from "@apollo/client/utilities";
const httpLink = new HttpLink({
uri: "http://localhost:4000/graphql",
});
const link = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink,
);

Fragments

Fragment Definition 📝

# Basic fragment
fragment UserInfo on User {
id
name
email
}
# Fragment with nested fields
fragment PostWithAuthor on Post {
id
title
content
author {
...UserInfo
}
}
# Fragment on interface
fragment NodeInfo on Node {
id
createdAt
}
# Fragment on union
fragment SearchResultInfo on SearchResult {
... on User {
id
name
}
... on Post {
id
title
}
}

Fragment Usage 🔗

# Use fragment in query
query {
user(id: "123") {
...UserInfo
}
}
# Use fragment in mutation
mutation {
createUser(input: $input) {
...UserInfo
}
}
# Inline fragments (type conditions)
query {
search(term: "graphql") {
... on User {
id
name
email
}
... on Post {
id
title
content
}
}
}

Fragment Composition 🧩

# Compose fragments
fragment UserBasic on User {
id
name
}
fragment UserFull on User {
...UserBasic
email
posts {
id
title
}
}
query {
user(id: "123") {
...UserFull
}
}

Variables

Variable Definition 📊

# Variable types must match schema
query GetUser($id: ID!) {
user(id: $id) {
name
}
}
# Multiple variables
query Search($term: String!, $limit: Int, $offset: Int) {
search(term: $term, limit: $limit, offset: $offset) {
id
title
}
}
# Variables with default values
query GetUsers($limit: Int = 10) {
users(limit: $limit) {
id
name
}
}
# List variables
mutation CreateUsers($inputs: [CreateUserInput!]!) {
createUsers(input: $inputs) {
id
name
}
}

Variable Usage 💻

// JavaScript/TypeScript example
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
// Apollo Client usage
const { data, loading, error } = useQuery(GET_USER, {
variables: {
id: "123",
},
});
// Variables object
const variables = {
id: "123",
includePosts: true,
limit: 20,
};

Variable Validation ✅

# ✅ Valid: Non-null variable provided
query ($id: ID!) {
user(id: $id) {
name
}
}
# Variables: { "id": "123" }
# ❌ Invalid: Non-null variable missing
query ($id: ID!) {
user(id: $id) {
name
}
}
# Variables: { }
# ✅ Valid: Nullable variable can be omitted
query ($limit: Int) {
users(limit: $limit) {
name
}
}
# Variables: { }
# ✅ Valid: List variable
mutation ($inputs: [CreateUserInput!]!) {
createUsers(input: $inputs) {
id
}
}
# Variables: { "inputs": [{ "name": "Alice" }] }

Directives

Built-in Directives 🎯

# @include - Include field if condition is true
query {
user {
id
name
email @include(if: $includeEmail)
}
}
# @skip - Skip field if condition is true
query {
user {
id
name
email @skip(if: $hideEmail)
}
}
# @deprecated - Mark field as deprecated (schema definition)
type User {
id: ID!
oldField: String @deprecated(reason: "Use newField instead")
newField: String!
}
# @specifiedBy - Document custom scalar (schema definition)
scalar DateTime @specifiedBy(url: "https://tools.ietf.org/html/rfc3339")

Custom Directives 🔧

# Define custom directive in schema
directive @auth(requires: Role!) on FIELD_DEFINITION
directive @rateLimit(max: Int, window: String) on FIELD_DEFINITION
directive @cacheControl(maxAge: Int, scope: CacheScope) on FIELD_DEFINITION
# Use custom directive
type Query {
user(id: ID!): User @auth(requires: USER)
posts: [Post!]! @rateLimit(max: 100, window: "1h")
publicPosts: [Post!]! @cacheControl(maxAge: 3600)
}
enum Role {
ADMIN
USER
GUEST
}
enum CacheScope {
PUBLIC
PRIVATE
}

Directive Implementation 💻

// Apollo Server directive example
const typeDefs = gql`
directive @upper on FIELD_DEFINITION
type Query {
hello: String @upper
}
`;
const resolvers = {
Query: {
hello: () => "hello world",
},
};
// Directive transformer
const upperDirectiveTransformer = (schema) => {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const upperDirective = getDirective(schema, fieldConfig, "upper")?.[0];
if (upperDirective) {
const { resolve = defaultFieldResolver } = fieldConfig;
fieldConfig.resolve = async (source, args, context, info) => {
const result = await resolve(source, args, context, info);
if (typeof result === "string") {
return result.toUpperCase();
}
return result;
};
}
return fieldConfig;
},
});
};

Resolvers

Basic Resolvers 🔨

// JavaScript resolver example
const resolvers = {
Query: {
// Simple resolver
hello: () => "Hello World!",
// Resolver with arguments
user: (parent, args, context, info) => {
return getUserById(args.id);
},
// Resolver with context
me: (parent, args, context) => {
return context.currentUser;
},
},
Mutation: {
createUser: (parent, args, context) => {
return createUser(args.input);
},
},
// Field resolver
User: {
posts: (parent, args, context) => {
return getPostsByUserId(parent.id);
},
},
};

Resolver Arguments 📥

// Resolver function signature
(parent, args, context, info) => {
// parent: Result from parent resolver
// args: Arguments provided to the field
// context: Shared context across resolvers
// info: Field-specific information
};
// Example with all arguments
const resolvers = {
Query: {
posts: (parent, args, context, info) => {
const { filter, limit, offset } = args;
const { db, currentUser } = context;
const fieldName = info.fieldName;
return db.posts.findMany({
where: filter,
take: limit,
skip: offset,
});
},
},
};

Async Resolvers ⚡

// Async/await resolvers
const resolvers = {
Query: {
user: async (parent, args) => {
const user = await db.user.findUnique({
where: { id: args.id },
});
return user;
},
users: async (parent, args) => {
const users = await db.user.findMany({
take: args.limit || 10,
skip: args.offset || 0,
});
return users;
},
},
User: {
posts: async (parent) => {
return await db.post.findMany({
where: { authorId: parent.id },
});
},
},
};

Resolver Context 🎯

// Context setup
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
// Extract token from request
const token = req.headers.authorization?.replace("Bearer ", "");
// Verify and decode token
const user = token ? verifyToken(token) : null;
return {
db,
currentUser: user,
dataSources: {
userAPI: new UserAPI(),
postAPI: new PostAPI(),
},
};
},
});
// Use context in resolvers
const resolvers = {
Query: {
me: (parent, args, context) => {
if (!context.currentUser) {
throw new AuthenticationError("Not authenticated");
}
return context.currentUser;
},
},
};

DataLoader Pattern 🚀

// DataLoader for batch loading (N+1 problem solution)
import DataLoader from "dataloader";
// Create DataLoader
const userLoader = new DataLoader(async (userIds) => {
const users = await db.user.findMany({
where: { id: { in: userIds } },
});
// Return users in same order as userIds
return userIds.map((id) => users.find((user) => user.id === id));
});
// Use in resolvers
const resolvers = {
Post: {
author: (parent, args, context) => {
return context.userLoader.load(parent.authorId);
},
},
};
// Context setup
context: () => ({
userLoader: new DataLoader(/* ... */),
postLoader: new DataLoader(/* ... */),
});

Error Handling

GraphQL Errors ❌

// Throw GraphQL errors
import { GraphQLError } from "graphql";
import {
ApolloError,
UserInputError,
AuthenticationError,
ForbiddenError,
} from "@apollo/server/errors";
const resolvers = {
Query: {
user: async (parent, args) => {
const user = await db.user.findUnique({
where: { id: args.id },
});
if (!user) {
throw new UserInputError("User not found", {
argumentName: "id",
code: "USER_NOT_FOUND",
});
}
return user;
},
},
Mutation: {
createPost: async (parent, args, context) => {
if (!context.currentUser) {
throw new AuthenticationError("You must be logged in");
}
if (!context.currentUser.canPost) {
throw new ForbiddenError("You do not have permission to post");
}
try {
return await db.post.create({
data: args.input,
});
} catch (error) {
throw new ApolloError("Failed to create post", "CREATE_POST_FAILED", {
originalError: error,
});
}
},
},
};

Error Response Format 📋

{
"errors": [
{
"message": "User not found",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": ["user"],
"extensions": {
"code": "USER_NOT_FOUND",
"argumentName": "id",
"timestamp": "2024-01-01T00:00:00Z"
}
}
],
"data": null
}

Partial Errors ⚠️

// Return partial data with errors
const resolvers = {
Query: {
users: async () => {
const users = await db.user.findMany();
// Some users might fail to load posts
return users.map((user) => {
try {
return {
...user,
posts: getPosts(user.id),
};
} catch (error) {
// Return user without posts, error added to errors array
return {
...user,
posts: null,
_error: error,
};
}
});
},
},
};
// Response includes both data and errors
// {
// "data": {
// "users": [
// { "id": "1", "name": "Alice", "posts": [...] },
// { "id": "2", "name": "Bob", "posts": null }
// ]
// },
// "errors": [
// {
// "message": "Failed to load posts",
// "path": ["users", 1, "posts"]
// }
// ]
// }

Custom Error Formatting 🎨

// Custom error formatter
const server = new ApolloServer({
typeDefs,
resolvers,
formatError: (err) => {
// Log error for debugging
console.error(err);
// Don't expose internal errors in production
if (err.extensions?.code === "INTERNAL_SERVER_ERROR") {
return new Error("Internal server error");
}
// Return custom error format
return {
message: err.message,
code: err.extensions?.code || "INTERNAL_ERROR",
timestamp: new Date().toISOString(),
};
},
});

Best Practices

Schema Design ✅

# ✅ DO: Use descriptive names
type User {
id: ID!
firstName: String!
lastName: String!
}
# ❌ DON'T: Use abbreviations
type User {
id: ID!
fn: String!
ln: String!
}
# ✅ DO: Use enums for fixed sets
enum UserRole {
ADMIN
USER
GUEST
}
# ❌ DON'T: Use strings for fixed sets
type User {
role: String! # Could be anything
}
# ✅ DO: Use input types for mutations
input CreateUserInput {
name: String!
email: String!
}
# ❌ DON'T: Use many arguments
mutation {
createUser(name: String!, email: String!, age: Int, role: String): User!
}
# ✅ DO: Version fields with @deprecated
type User {
oldField: String @deprecated(reason: "Use newField")
newField: String!
}

Query Optimization 🚀

# ✅ DO: Request only needed fields
query {
user(id: "123") {
id
name
}
}
# ❌ DON'T: Request all fields
query {
user(id: "123") {
id
name
email
age
bio
avatar
posts {
id
title
content
comments {
id
content
}
}
}
}
# ✅ DO: Use pagination
query {
posts(limit: 10, offset: 0) {
id
title
}
}
# ❌ DON'T: Fetch all records
query {
posts {
id
title
}
}
# ✅ DO: Use fragments for reusability
fragment UserFields on User {
id
name
email
}
# ❌ DON'T: Repeat fields
query {
user {
id
name
email
}
post {
author {
id
name
email
}
}
}

Security 🔒

// ✅ DO: Validate and sanitize inputs
const resolvers = {
Mutation: {
createPost: async (parent, args, context) => {
// Validate input
if (!args.input.title || args.input.title.length < 3) {
throw new UserInputError("Title must be at least 3 characters");
}
// Sanitize input
const sanitizedTitle = sanitize(args.input.title);
return await db.post.create({
data: {
...args.input,
title: sanitizedTitle,
authorId: context.currentUser.id,
},
});
},
},
};
// ✅ DO: Implement rate limiting
import rateLimit from "express-rate-limit";
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});
// ✅ DO: Use query depth limiting
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
depthLimit(10), // Maximum query depth of 10
],
});
// ✅ DO: Implement query complexity analysis
import { createComplexityLimitRule } from "graphql-query-complexity";
const complexityRule = createComplexityLimitRule(1000, {
scalarCost: 1,
objectCost: 10,
listFactor: 10,
});

Performance ⚡

// ✅ DO: Use DataLoader for batch loading
const userLoader = new DataLoader(async (ids) => {
const users = await db.user.findMany({ where: { id: { in: ids } } });
return ids.map((id) => users.find((u) => u.id === id));
});
// ❌ DON'T: Load data in loops (N+1 problem)
const resolvers = {
Post: {
author: async (parent) => {
// This runs for each post - N+1 problem!
return await db.user.findUnique({ where: { id: parent.authorId } });
},
},
};
// ✅ DO: Use field-level caching
const resolvers = {
Query: {
posts: async () => {
return await cache.getOrSet(
"posts",
async () => {
return await db.post.findMany();
},
3600,
); // Cache for 1 hour
},
},
};
// ✅ DO: Implement query result caching
import responseCachePlugin from "@apollo/server-plugin-response-cache";
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
responseCachePlugin({
ttl: 300, // Cache for 5 minutes
}),
],
});

Common Pitfalls

N+1 Query Problem ⚠️

// ❌ PROBLEM: N+1 queries
const resolvers = {
Query: {
posts: async () => {
return await db.post.findMany(); // 1 query
},
},
Post: {
author: async (parent) => {
// This runs N times (once per post) - N+1 problem!
return await db.user.findUnique({ where: { id: parent.authorId } });
},
},
};
// ✅ SOLUTION: Use DataLoader
const userLoader = new DataLoader(async (ids) => {
const users = await db.user.findMany({ where: { id: { in: ids } } });
return ids.map((id) => users.find((u) => u.id === id));
});
const resolvers = {
Post: {
author: (parent, args, context) => {
return context.userLoader.load(parent.authorId); // Batched!
},
},
};

Over-fetching Data 📊

# ❌ PROBLEM: Fetching unnecessary data
query {
user(id: "123") {
id
name
email
bio
avatar
posts {
id
title
content
comments {
id
content
author {
id
name
email
}
}
}
}
}
# ✅ SOLUTION: Request only needed fields
query {
user(id: "123") {
id
name
posts {
title
}
}
}

Missing Error Handling 🚨

// ❌ PROBLEM: No error handling
const resolvers = {
Query: {
user: async (parent, args) => {
const user = await db.user.findUnique({ where: { id: args.id } });
return user; // Returns null if not found, no error
},
},
};
// ✅ SOLUTION: Proper error handling
const resolvers = {
Query: {
user: async (parent, args) => {
const user = await db.user.findUnique({ where: { id: args.id } });
if (!user) {
throw new UserInputError("User not found", {
code: "USER_NOT_FOUND",
});
}
return user;
},
},
};

Circular Dependencies 🔄

# ❌ PROBLEM: Circular type dependencies
type User {
id: ID!
posts: [Post!]!
}
type Post {
id: ID!
author: User!
comments: [Comment!]!
}
type Comment {
id: ID!
post: Post!
author: User!
}
# ✅ SOLUTION: Use nullable fields or limit depth
type User {
id: ID!
posts(limit: Int = 10): [Post!]!
}
type Post {
id: ID!
author: User!
comments(limit: Int = 10): [Comment!]!
}
type Comment {
id: ID!
post: Post!
author: User!
}
# Also implement query depth limiting in server

Mutation Side Effects ⚠️

// ❌ PROBLEM: Mutations with unexpected side effects
const resolvers = {
Mutation: {
updateUser: async (parent, args) => {
// Updates user
const user = await db.user.update({
where: { id: args.id },
data: args.input,
});
// Unexpected side effect: sends email
await sendEmail(user.email, "Profile updated");
// Unexpected side effect: invalidates cache
await cache.delete(`user:${args.id}`);
return user;
},
},
};
// ✅ SOLUTION: Keep mutations focused, use plugins/hooks for side effects
const resolvers = {
Mutation: {
updateUser: async (parent, args) => {
return await db.user.update({
where: { id: args.id },
data: args.input,
});
},
},
};
// Handle side effects in plugins or hooks
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
{
requestDidStart() {
return {
willSendResponse(requestContext) {
// Handle side effects after mutation
if (requestContext.operation.operation === "mutation") {
// Send emails, invalidate cache, etc.
}
},
};
},
},
],
});

Real-World Examples

Authentication Flow 🔐

# Schema
type Mutation {
login(email: String!, password: String!): AuthPayload!
signup(input: SignupInput!): AuthPayload!
logout: Boolean!
}
type AuthPayload {
token: String!
user: User!
}
input SignupInput {
name: String!
email: String!
password: String!
}
# Query
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
user {
id
name
email
}
}
}
// Resolver implementation
const resolvers = {
Mutation: {
login: async (parent, args) => {
const user = await db.user.findUnique({
where: { email: args.email },
});
if (!user || !(await bcrypt.compare(args.password, user.passwordHash))) {
throw new AuthenticationError("Invalid credentials");
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
return {
token,
user,
};
},
},
};

Pagination Pattern 📄

# Schema
type Query {
posts(first: Int, after: String, last: Int, before: String): PostConnection!
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
}
type PostEdge {
node: Post!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# Query
query {
posts(first: 10, after: "cursor123") {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}

File Upload 📤

# Schema (using graphql-upload)
scalar Upload
type Mutation {
uploadAvatar(file: Upload!): User!
}
# Query
mutation UploadAvatar($file: Upload!) {
uploadAvatar(file: $file) {
id
avatarUrl
}
}
// Resolver implementation
import { GraphQLUpload } from "graphql-upload";
const resolvers = {
Upload: GraphQLUpload,
Mutation: {
uploadAvatar: async (parent, { file }, context) => {
const { createReadStream, filename, mimetype } = await file;
// Upload to storage (e.g., S3, Cloudinary)
const avatarUrl = await uploadToStorage(createReadStream, filename);
// Update user
return await db.user.update({
where: { id: context.currentUser.id },
data: { avatarUrl },
});
},
},
};

Real-time Chat 💬

# Schema
type Subscription {
messageAdded(roomId: ID!): Message!
}
type Mutation {
sendMessage(roomId: ID!, content: String!): Message!
}
type Message {
id: ID!
content: String!
author: User!
roomId: ID!
createdAt: String!
}
# Subscription
subscription OnMessageAdded($roomId: ID!) {
messageAdded(roomId: $roomId) {
id
content
author {
id
name
}
createdAt
}
}
// Resolver with PubSub
import { PubSub } from "graphql-subscriptions";
const pubsub = new PubSub();
const resolvers = {
Mutation: {
sendMessage: async (parent, args, context) => {
const message = await db.message.create({
data: {
content: args.content,
authorId: context.currentUser.id,
roomId: args.roomId,
},
include: { author: true },
});
// Publish to subscription
await pubsub.publish("MESSAGE_ADDED", {
messageAdded: message,
});
return message;
},
},
Subscription: {
messageAdded: {
subscribe: (parent, args) => {
return pubsub.asyncIterator(["MESSAGE_ADDED"]);
},
resolve: (payload) => {
return payload.messageAdded;
},
},
},
};