> graphql
Build and consume GraphQL APIs. Use when a user asks to create a GraphQL server, write GraphQL schemas, implement resolvers, set up subscriptions, build a GraphQL API, add authentication to GraphQL, optimize queries with DataLoader, implement pagination, handle file uploads, generate types from schema, consume a GraphQL endpoint, or migrate from REST to GraphQL. Covers Apollo Server, Apollo Client, schema design, resolvers, subscriptions, federation, and production patterns.
curl "https://skillshub.wtf/TerminalSkills/skills/graphql?format=md"GraphQL
Overview
Design, build, and consume GraphQL APIs. This skill covers schema-first and code-first approaches, resolver patterns, real-time subscriptions, authentication, performance optimization with DataLoader, pagination, federation for microservices, and client-side consumption with Apollo Client.
Instructions
Step 1: Project Setup
Server (Apollo Server):
npm install @apollo/server graphql
npm install @apollo/server express cors # With Express
npm install dataloader # For N+1 prevention
Client (Apollo Client):
npm install @apollo/client graphql
Type generation:
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers
Step 2: Schema Design
type Query {
user(id: ID!): User
users(filter: UserFilter, pagination: PaginationInput): UserConnection!
feed(cursor: String, limit: Int = 20): PostConnection!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
createPost(input: CreatePostInput!): Post!
likePost(id: ID!): Post!
}
type Subscription {
postCreated: Post!
}
type User {
id: ID!
email: String!
name: String!
posts(limit: Int = 10): [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
likes: Int!
createdAt: DateTime!
}
# Relay-style cursor pagination
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge { node: User!; cursor: String! }
type PageInfo { hasNextPage: Boolean!; hasPreviousPage: Boolean!; endCursor: String }
input CreateUserInput { email: String!; name: String! }
input UpdateUserInput { name: String; avatar: String }
input CreatePostInput { title: String!; content: String!; tags: [String!] }
input PaginationInput { first: Int; after: String }
scalar DateTime
Schema design rules: use ! for non-nullable fields, input types for mutations, Relay-style connections for pagination, descriptive verb names (createUser, not addUser).
Step 3: Resolvers
import { GraphQLError } from 'graphql';
export const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => dataSources.users.getById(id),
users: async (_, { filter, pagination }, { dataSources }) => {
const { first = 20, after } = pagination || {};
const result = await dataSources.users.getMany({ filter, first, after });
return {
edges: result.items.map(item => ({
node: item, cursor: Buffer.from(item.id).toString('base64'),
})),
pageInfo: { hasNextPage: result.hasMore, hasPreviousPage: !!after,
endCursor: result.items.at(-1) ? Buffer.from(result.items.at(-1).id).toString('base64') : null },
totalCount: result.totalCount,
};
},
},
Mutation: {
createUser: async (_, { input }, { dataSources, user }) => {
if (!user) throw new GraphQLError('Not authenticated', { extensions: { code: 'UNAUTHENTICATED' } });
return dataSources.users.create(input);
},
createPost: async (_, { input }, { dataSources, user }) => {
if (!user) throw new GraphQLError('Not authenticated', { extensions: { code: 'UNAUTHENTICATED' } });
return dataSources.posts.create({ ...input, authorId: user.id });
},
},
User: { posts: async (parent, { limit }, { dataSources }) => dataSources.posts.getByAuthor(parent.id, limit) },
Post: { author: async (parent, _, { dataSources }) => dataSources.users.getById(parent.authorId) },
};
Step 4: Server Setup with Subscriptions
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import express from 'express';
import cors from 'cors';
const schema = makeExecutableSchema({ typeDefs, resolvers });
const app = express();
const httpServer = require('http').createServer(app);
const wsServer = new WebSocketServer({ server: httpServer, path: '/graphql' });
useServer({ schema }, wsServer);
const server = new ApolloServer({ schema });
await server.start();
app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
context: async ({ req }) => ({
user: await getUserFromToken(req.headers.authorization?.replace('Bearer ', '')),
dataSources: createDataSources(),
}),
}));
httpServer.listen(4000);
Step 5: DataLoader (N+1 Problem)
import DataLoader from 'dataloader';
export function createDataSources() {
const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findMany({ where: { id: { in: ids } } });
const userMap = new Map(users.map(u => [u.id, u]));
return ids.map(id => userMap.get(id) || null); // Must return in same order as input
});
return {
users: { getById: (id) => userLoader.load(id), create: (input) => db.users.create({ data: input }) },
posts: { getByAuthor: (authorId, limit) => db.posts.findMany({ where: { authorId }, take: limit }) },
};
}
Step 6: Apollo Client (React)
import { ApolloClient, InMemoryCache, gql, useQuery, useMutation } from '@apollo/client';
const client = new ApolloClient({ uri: '/graphql', cache: new InMemoryCache() });
const GET_FEED = gql`
query GetFeed($cursor: String) {
feed(cursor: $cursor, limit: 20) {
edges { node { id title author { name } likes } cursor }
pageInfo { hasNextPage endCursor }
}
}
`;
function Feed() {
const { data, loading, fetchMore } = useQuery(GET_FEED);
if (loading) return <p>Loading...</p>;
return (
<div>
{data.feed.edges.map(({ node }) => (
<article key={node.id}><h2>{node.title}</h2><p>By {node.author.name}</p></article>
))}
{data.feed.pageInfo.hasNextPage && (
<button onClick={() => fetchMore({ variables: { cursor: data.feed.pageInfo.endCursor } })}>
Load more
</button>
)}
</div>
);
}
Step 7: Security
import depthLimit from 'graphql-depth-limit';
const server = new ApolloServer({
schema,
validationRules: [depthLimit(7)], // Prevent deeply nested abuse queries
});
// Use @cacheControl directive for response caching
// type Query { user(id: ID!): User @cacheControl(maxAge: 60) }
Examples
Example 1: Build a blog API with cursor pagination
User prompt: "Create a GraphQL API for a blog with users, posts, and comments. Include cursor-based pagination for the post feed and authentication for mutations."
The agent will:
- Define the schema with
User,Post,Commenttypes,PostConnectionfor Relay-style pagination, and input types for mutations - Set up Apollo Server with Express, configure JWT-based context extraction
- Implement resolvers with DataLoader to batch user lookups (preventing N+1 queries when loading post authors)
- Add cursor pagination using base64-encoded IDs as cursors in the
feedquery - Protect mutation resolvers with authentication checks that throw
UNAUTHENTICATEDGraphQL errors
Example 2: Add real-time post notifications to a React app
User prompt: "Add a real-time subscription so users see new posts appear in the feed without refreshing the page."
The agent will:
- Add a
Subscription { postCreated: Post! }type to the schema - Set up WebSocket server alongside the HTTP server using
graphql-ws - Implement a
PubSubinstance and publish events in thecreatePostmutation resolver - On the client, use
useSubscriptionfrom Apollo Client with aPOST_CREATEDsubscription query - Update the Apollo Client cache when new posts arrive via the subscription callback
Guidelines
- Schema-first design — agree on the schema before coding resolvers
- Always use DataLoader — the N+1 problem is GraphQL's biggest gotcha
- Cursor pagination — offset breaks on large datasets; cursors are stable
- Input types for mutations — cleaner, more evolvable than inline arguments
- Error codes in extensions —
UNAUTHENTICATED,FORBIDDEN,NOT_FOUNDfor client handling - Depth and complexity limits — prevent abusive nested queries in production
- Codegen for types — hand-typing GraphQL types is error-prone and wastes time
- Cache normalization — Apollo Client's
InMemoryCachededuplicates byid+__typename - Subscriptions only when needed — polling is simpler if real-time isn't critical
- Federation for microservices — split schema across services with Apollo Federation
> related_skills --same-repo
> zustand
You are an expert in Zustand, the small, fast, and scalable state management library for React. You help developers manage global state without boilerplate using Zustand's hook-based stores, selectors for performance, middleware (persist, devtools, immer), computed values, and async actions — replacing Redux complexity with a simple, un-opinionated API in under 1KB.
> zoho
Integrate and automate Zoho products. Use when a user asks to work with Zoho CRM, Zoho Books, Zoho Desk, Zoho Projects, Zoho Mail, or Zoho Creator, build custom integrations via Zoho APIs, automate workflows with Deluge scripting, sync data between Zoho apps and external systems, manage leads and deals, automate invoicing, build custom Zoho Creator apps, set up webhooks, or manage Zoho organization settings. Covers Zoho CRM, Books, Desk, Projects, Creator, and cross-product integrations.
> zod
You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.
> zipkin
Deploy and configure Zipkin for distributed tracing and request flow visualization. Use when a user needs to set up trace collection, instrument Java/Spring or other services with Zipkin, analyze service dependencies, or configure storage backends for trace data.