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
- Schema Definition
- Queries
- Mutations
- Subscriptions
- Fragments
- Variables
- Directives
- Resolvers
- Error Handling
- Best Practices
- Common Pitfalls
Prerequisites
GraphQL Version: This cheatsheet targets GraphQL.js 16.0+ and Apollo Server 4.0+. Some features require specific versions (noted inline).
# Install GraphQL core librarypnpm add graphql
# Install Apollo Server (Express)pnpm add @apollo/server expresspnpm 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/typescriptNode.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 typesenum UserRole { ADMIN USER GUEST}
type User { role: UserRole! # Must be one of: ADMIN, USER, GUEST}
# List typestype 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 typesunion SearchResult = User | Post | Comment
type Query { search(term: String!): [SearchResult!]!}
# Interface typesinterface 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 mutationsinput 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 📄
scalar DateTimescalar 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 fieldquery { user { name }}
# Query multiple fieldsquery { user { id name email }}
# Query with argumentsquery { user(id: "123") { id name email }}
# Query nested fieldsquery { user(id: "123") { id name posts { id title comments { id content } } }}Query with Aliases 🏷️
# Use aliases to query same field multiple timesquery { firstUser: user(id: "1") { name } secondUser: user(id: "2") { name }}
# Response:# {# "data": {# "firstUser": { "name": "Alice" },# "secondUser": { "name": "Bob" }# }# }Query with Fragments 📋
# Define fragmentfragment UserFields on User { id name email}
# Use fragment in queryquery { user(id: "123") { ...UserFields posts { title } }}
# Multiple fragmentsfragment UserBasic on User { id name}
fragment UserDetails on User { email createdAt}
query { user(id: "123") { ...UserBasic ...UserDetails }}Query with Variables 🔄
# Query definition with variablesquery 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 mutationmutation { createUser(input: { name: "Alice" email: "alice@example.com" }) { id name email }}
# Mutation with variablesmutation 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 requestmutation { createUser(input: { name: "Alice", email: "alice@example.com" }) { id name } createPost(input: { title: "Hello", content: "World" }) { id title }}
# Note: Mutations execute sequentially, not in parallelMutation with Error Handling ⚠️
# Mutation that may failmutation { 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 patternmutation 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 updatessubscription { userCreated { id name email }}
# Subscription with argumentssubscription { messageAdded(roomId: "123") { id content author { name } createdAt }}
# Subscription with variablessubscription OnMessageAdded($roomId: ID!) { messageAdded(roomId: $roomId) { id content author { name } }}WebSocket Connection 🔌
// Apollo Client WebSocket setupimport { 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 fragmentfragment UserInfo on User { id name email}
# Fragment with nested fieldsfragment PostWithAuthor on Post { id title content author { ...UserInfo }}
# Fragment on interfacefragment NodeInfo on Node { id createdAt}
# Fragment on unionfragment SearchResultInfo on SearchResult { ... on User { id name } ... on Post { id title }}Fragment Usage 🔗
# Use fragment in queryquery { user(id: "123") { ...UserInfo }}
# Use fragment in mutationmutation { 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 fragmentsfragment 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 schemaquery GetUser($id: ID!) { user(id: $id) { name }}
# Multiple variablesquery Search($term: String!, $limit: Int, $offset: Int) { search(term: $term, limit: $limit, offset: $offset) { id title }}
# Variables with default valuesquery GetUsers($limit: Int = 10) { users(limit: $limit) { id name }}
# List variablesmutation CreateUsers($inputs: [CreateUserInput!]!) { createUsers(input: $inputs) { id name }}Variable Usage 💻
// JavaScript/TypeScript exampleconst GET_USER = gql` query GetUser($id: ID!) { user(id: $id) { id name email } }`;
// Apollo Client usageconst { data, loading, error } = useQuery(GET_USER, { variables: { id: "123", },});
// Variables objectconst variables = { id: "123", includePosts: true, limit: 20,};Variable Validation ✅
# ✅ Valid: Non-null variable providedquery ($id: ID!) { user(id: $id) { name }}# Variables: { "id": "123" }
# ❌ Invalid: Non-null variable missingquery ($id: ID!) { user(id: $id) { name }}# Variables: { }
# ✅ Valid: Nullable variable can be omittedquery ($limit: Int) { users(limit: $limit) { name }}# Variables: { }
# ✅ Valid: List variablemutation ($inputs: [CreateUserInput!]!) { createUsers(input: $inputs) { id }}# Variables: { "inputs": [{ "name": "Alice" }] }Directives
Built-in Directives 🎯
# @include - Include field if condition is truequery { user { id name email @include(if: $includeEmail) }}
# @skip - Skip field if condition is truequery { 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 schemadirective @auth(requires: Role!) on FIELD_DEFINITIONdirective @rateLimit(max: Int, window: String) on FIELD_DEFINITIONdirective @cacheControl(maxAge: Int, scope: CacheScope) on FIELD_DEFINITION
# Use custom directivetype 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 exampleconst typeDefs = gql` directive @upper on FIELD_DEFINITION
type Query { hello: String @upper }`;
const resolvers = { Query: { hello: () => "hello world", },};
// Directive transformerconst 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 exampleconst 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 argumentsconst 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 resolversconst 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 setupconst 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 resolversconst 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 DataLoaderconst 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 resolversconst resolvers = { Post: { author: (parent, args, context) => { return context.userLoader.load(parent.authorId); }, },};
// Context setupcontext: () => ({ userLoader: new DataLoader(/* ... */), postLoader: new DataLoader(/* ... */),});Error Handling
GraphQL Errors ❌
// Throw GraphQL errorsimport { 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 errorsconst 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 formatterconst 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 namestype User { id: ID! firstName: String! lastName: String!}
# ❌ DON'T: Use abbreviationstype User { id: ID! fn: String! ln: String!}
# ✅ DO: Use enums for fixed setsenum UserRole { ADMIN USER GUEST}
# ❌ DON'T: Use strings for fixed setstype User { role: String! # Could be anything}
# ✅ DO: Use input types for mutationsinput CreateUserInput { name: String! email: String!}
# ❌ DON'T: Use many argumentsmutation { createUser(name: String!, email: String!, age: Int, role: String): User!}
# ✅ DO: Version fields with @deprecatedtype User { oldField: String @deprecated(reason: "Use newField") newField: String!}Query Optimization 🚀
# ✅ DO: Request only needed fieldsquery { user(id: "123") { id name }}
# ❌ DON'T: Request all fieldsquery { user(id: "123") { id name email age bio avatar posts { id title content comments { id content } } }}
# ✅ DO: Use paginationquery { posts(limit: 10, offset: 0) { id title }}
# ❌ DON'T: Fetch all recordsquery { posts { id title }}
# ✅ DO: Use fragments for reusabilityfragment UserFields on User { id name email}
# ❌ DON'T: Repeat fieldsquery { user { id name email } post { author { id name email } }}Security 🔒
// ✅ DO: Validate and sanitize inputsconst 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 limitingimport 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 limitingconst server = new ApolloServer({ typeDefs, resolvers, validationRules: [ depthLimit(10), // Maximum query depth of 10 ],});
// ✅ DO: Implement query complexity analysisimport { createComplexityLimitRule } from "graphql-query-complexity";
const complexityRule = createComplexityLimitRule(1000, { scalarCost: 1, objectCost: 10, listFactor: 10,});Performance ⚡
// ✅ DO: Use DataLoader for batch loadingconst 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 cachingconst resolvers = { Query: { posts: async () => { return await cache.getOrSet( "posts", async () => { return await db.post.findMany(); }, 3600, ); // Cache for 1 hour }, },};
// ✅ DO: Implement query result cachingimport 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 queriesconst 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 DataLoaderconst 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 dataquery { user(id: "123") { id name email bio avatar posts { id title content comments { id content author { id name email } } } }}
# ✅ SOLUTION: Request only needed fieldsquery { user(id: "123") { id name posts { title } }}Missing Error Handling 🚨
// ❌ PROBLEM: No error handlingconst 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 handlingconst 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 dependenciestype 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 depthtype 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 serverMutation Side Effects ⚠️
// ❌ PROBLEM: Mutations with unexpected side effectsconst 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 effectsconst resolvers = { Mutation: { updateUser: async (parent, args) => { return await db.user.update({ where: { id: args.id }, data: args.input, }); }, },};
// Handle side effects in plugins or hooksconst 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 🔐
# Schematype 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!}
# Querymutation Login($email: String!, $password: String!) { login(email: $email, password: $password) { token user { id name email } }}// Resolver implementationconst 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 📄
# Schematype 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}
# Queryquery { 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!}
# Querymutation UploadAvatar($file: Upload!) { uploadAvatar(file: $file) { id avatarUrl }}// Resolver implementationimport { 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 💬
# Schematype 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!}
# Subscriptionsubscription OnMessageAdded($roomId: ID!) { messageAdded(roomId: $roomId) { id content author { id name } createdAt }}// Resolver with PubSubimport { 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; }, }, },};