[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6EF42G25EKP7ZXDM10S0CXT":3,"skill-same:01M6EF42G25EKP7ZXDM10S0CXT":46},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":21,"frontMatter":22,"refName":23,"commitSha":24,"skillDirectory":25,"forksCount":26,"repositoryHtmlUrl":27,"repositoryHomepageUrl":28,"ownerHtmlUrl":29,"ownerBlogUrl":6,"ownerDescription":30,"updatedAt":31,"install":32,"resources":45},"01M6EF42G25EKP7ZXDM10S0CXT","graphql",null,"ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FTerminalSkills\u002Fskills\u002Fgraphql\u002FSKILL.md",[],[18],"GraphQL","2026-11-10 10:12:30",false,"# GraphQL\n\n## Overview\n\nDesign, 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.\n\n## Instructions\n\n### Step 1: Project Setup\n\n**Server (Apollo Server):**\n```bash\nnpm install @apollo\u002Fserver graphql\nnpm install @apollo\u002Fserver express cors  # With Express\nnpm install dataloader                    # For N+1 prevention\n```\n\n**Client (Apollo Client):**\n```bash\nnpm install @apollo\u002Fclient graphql\n```\n\n**Type generation:**\n```bash\nnpm install -D @graphql-codegen\u002Fcli @graphql-codegen\u002Ftypescript @graphql-codegen\u002Ftypescript-resolvers\n```\n\n### Step 2: Schema Design\n\n```graphql\ntype Query {\n  user(id: ID!): User\n  users(filter: UserFilter, pagination: PaginationInput): UserConnection!\n  feed(cursor: String, limit: Int = 20): PostConnection!\n}\n\ntype Mutation {\n  createUser(input: CreateUserInput!): User!\n  updateUser(id: ID!, input: UpdateUserInput!): User!\n  createPost(input: CreatePostInput!): Post!\n  likePost(id: ID!): Post!\n}\n\ntype Subscription {\n  postCreated: Post!\n}\n\ntype User {\n  id: ID!\n  email: String!\n  name: String!\n  posts(limit: Int = 10): [Post!]!\n  createdAt: DateTime!\n}\n\ntype Post {\n  id: ID!\n  title: String!\n  content: String!\n  author: User!\n  comments: [Comment!]!\n  likes: Int!\n  createdAt: DateTime!\n}\n\n# Relay-style cursor pagination\ntype UserConnection {\n  edges: [UserEdge!]!\n  pageInfo: PageInfo!\n  totalCount: Int!\n}\ntype UserEdge { node: User!; cursor: String! }\ntype PageInfo { hasNextPage: Boolean!; hasPreviousPage: Boolean!; endCursor: String }\n\ninput CreateUserInput { email: String!; name: String! }\ninput UpdateUserInput { name: String; avatar: String }\ninput CreatePostInput { title: String!; content: String!; tags: [String!] }\ninput PaginationInput { first: Int; after: String }\nscalar DateTime\n```\n\nSchema design rules: use `!` for non-nullable fields, input types for mutations, Relay-style connections for pagination, descriptive verb names (`createUser`, not `addUser`).\n\n### Step 3: Resolvers\n\n```javascript\nimport { GraphQLError } from 'graphql';\n\nexport const resolvers = {\n  Query: {\n    user: async (_, { id }, { dataSources }) => dataSources.users.getById(id),\n    users: async (_, { filter, pagination }, { dataSources }) => {\n      const { first = 20, after } = pagination || {};\n      const result = await dataSources.users.getMany({ filter, first, after });\n      return {\n        edges: result.items.map(item => ({\n          node: item, cursor: Buffer.from(item.id).toString('base64'),\n        })),\n        pageInfo: { hasNextPage: result.hasMore, hasPreviousPage: !!after,\n          endCursor: result.items.at(-1) ? Buffer.from(result.items.at(-1).id).toString('base64') : null },\n        totalCount: result.totalCount,\n      };\n    },\n  },\n  Mutation: {\n    createUser: async (_, { input }, { dataSources, user }) => {\n      if (!user) throw new GraphQLError('Not authenticated', { extensions: { code: 'UNAUTHENTICATED' } });\n      return dataSources.users.create(input);\n    },\n    createPost: async (_, { input }, { dataSources, user }) => {\n      if (!user) throw new GraphQLError('Not authenticated', { extensions: { code: 'UNAUTHENTICATED' } });\n      return dataSources.posts.create({ ...input, authorId: user.id });\n    },\n  },\n  User: { posts: async (parent, { limit }, { dataSources }) => dataSources.posts.getByAuthor(parent.id, limit) },\n  Post: { author: async (parent, _, { dataSources }) => dataSources.users.getById(parent.authorId) },\n};\n```\n\n### Step 4: Server Setup with Subscriptions\n\n```javascript\nimport { ApolloServer } from '@apollo\u002Fserver';\nimport { expressMiddleware } from '@apollo\u002Fserver\u002Fexpress4';\nimport { makeExecutableSchema } from '@graphql-tools\u002Fschema';\nimport { WebSocketServer } from 'ws';\nimport { useServer } from 'graphql-ws\u002Flib\u002Fuse\u002Fws';\nimport express from 'express';\nimport cors from 'cors';\n\nconst schema = makeExecutableSchema({ typeDefs, resolvers });\nconst app = express();\nconst httpServer = require('http').createServer(app);\nconst wsServer = new WebSocketServer({ server: httpServer, path: '\u002Fgraphql' });\nuseServer({ schema }, wsServer);\n\nconst server = new ApolloServer({ schema });\nawait server.start();\napp.use('\u002Fgraphql', cors(), express.json(), expressMiddleware(server, {\n  context: async ({ req }) => ({\n    user: await getUserFromToken(req.headers.authorization?.replace('Bearer ', '')),\n    dataSources: createDataSources(),\n  }),\n}));\nhttpServer.listen(4000);\n```\n\n### Step 5: DataLoader (N+1 Problem)\n\n```javascript\nimport DataLoader from 'dataloader';\n\nexport function createDataSources() {\n  const userLoader = new DataLoader(async (ids) => {\n    const users = await db.users.findMany({ where: { id: { in: ids } } });\n    const userMap = new Map(users.map(u => [u.id, u]));\n    return ids.map(id => userMap.get(id) || null); \u002F\u002F Must return in same order as input\n  });\n  return {\n    users: { getById: (id) => userLoader.load(id), create: (input) => db.users.create({ data: input }) },\n    posts: { getByAuthor: (authorId, limit) => db.posts.findMany({ where: { authorId }, take: limit }) },\n  };\n}\n```\n\n### Step 6: Apollo Client (React)\n\n```jsx\nimport { ApolloClient, InMemoryCache, gql, useQuery, useMutation } from '@apollo\u002Fclient';\n\nconst client = new ApolloClient({ uri: '\u002Fgraphql', cache: new InMemoryCache() });\n\nconst GET_FEED = gql`\n  query GetFeed($cursor: String) {\n    feed(cursor: $cursor, limit: 20) {\n      edges { node { id title author { name } likes } cursor }\n      pageInfo { hasNextPage endCursor }\n    }\n  }\n`;\n\nfunction Feed() {\n  const { data, loading, fetchMore } = useQuery(GET_FEED);\n  if (loading) return \u003Cp>Loading...\u003C\u002Fp>;\n  return (\n    \u003Cdiv>\n      {data.feed.edges.map(({ node }) => (\n        \u003Carticle key={node.id}>\u003Ch2>{node.title}\u003C\u002Fh2>\u003Cp>By {node.author.name}\u003C\u002Fp>\u003C\u002Farticle>\n      ))}\n      {data.feed.pageInfo.hasNextPage && (\n        \u003Cbutton onClick={() => fetchMore({ variables: { cursor: data.feed.pageInfo.endCursor } })}>\n          Load more\n        \u003C\u002Fbutton>\n      )}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Step 7: Security\n\n```javascript\nimport depthLimit from 'graphql-depth-limit';\n\nconst server = new ApolloServer({\n  schema,\n  validationRules: [depthLimit(7)], \u002F\u002F Prevent deeply nested abuse queries\n});\n\n\u002F\u002F Use @cacheControl directive for response caching\n\u002F\u002F type Query { user(id: ID!): User @cacheControl(maxAge: 60) }\n```\n\n## Examples\n\n### Example 1: Build a blog API with cursor pagination\n**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.\"\n\nThe agent will:\n1. Define the schema with `User`, `Post`, `Comment` types, `PostConnection` for Relay-style pagination, and input types for mutations\n2. Set up Apollo Server with Express, configure JWT-based context extraction\n3. Implement resolvers with DataLoader to batch user lookups (preventing N+1 queries when loading post authors)\n4. Add cursor pagination using base64-encoded IDs as cursors in the `feed` query\n5. Protect mutation resolvers with authentication checks that throw `UNAUTHENTICATED` GraphQL errors\n\n### Example 2: Add real-time post notifications to a React app\n**User prompt:** \"Add a real-time subscription so users see new posts appear in the feed without refreshing the page.\"\n\nThe agent will:\n1. Add a `Subscription { postCreated: Post! }` type to the schema\n2. Set up WebSocket server alongside the HTTP server using `graphql-ws`\n3. Implement a `PubSub` instance and publish events in the `createPost` mutation resolver\n4. On the client, use `useSubscription` from Apollo Client with a `POST_CREATED` subscription query\n5. Update the Apollo Client cache when new posts arrive via the subscription callback\n\n## Guidelines\n\n1. **Schema-first design** — agree on the schema before coding resolvers\n2. **Always use DataLoader** — the N+1 problem is GraphQL's biggest gotcha\n3. **Cursor pagination** — offset breaks on large datasets; cursors are stable\n4. **Input types for mutations** — cleaner, more evolvable than inline arguments\n5. **Error codes in extensions** — `UNAUTHENTICATED`, `FORBIDDEN`, `NOT_FOUND` for client handling\n6. **Depth and complexity limits** — prevent abusive nested queries in production\n7. **Codegen for types** — hand-typing GraphQL types is error-prone and wastes time\n8. **Cache normalization** — Apollo Client's `InMemoryCache` deduplicates by `id` + `__typename`\n9. **Subscriptions only when needed** — polling is simpler if real-time isn't critical\n10. **Federation for microservices** — split schema across services with Apollo Federation\n",{},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002FTerminalSkills\u002Fskills\u002Fgraphql",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":7,"skillName":5,"commands":33,"prompt":43,"zipAvailable":44},[34,37,40],{"tool":35,"command":36},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill graphql",{"tool":38,"command":39},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill graphql",{"tool":41,"command":42},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill graphql","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FTerminalSkills\u002Fskills\u002Fgraphql\u002FSKILL.md",true,[],[47,61,71,80,86,93],{"skillNo":48,"name":49,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":50,"categories":51,"keywords":57,"publishedAt":19},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[52],{"categoryNo":53,"categoryName":54,"taxonomyType":55,"taxonomyTypeDesc":56},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[58,59,60],"model","model hierarchy","model hierarchy skill",{"skillNo":62,"name":63,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":64,"categories":65,"keywords":67,"publishedAt":19},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[66],{"categoryNo":53,"categoryName":54,"taxonomyType":55,"taxonomyTypeDesc":56},[68,69,70],"MCP","zoom","zoom mcp",{"skillNo":72,"name":73,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":74,"categories":75,"keywords":76,"publishedAt":19},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[77,78,79,69],"sdk","zoom apps sdk","apps",{"skillNo":81,"name":82,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":19},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[82],{"skillNo":87,"name":88,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":89,"categories":90,"keywords":92,"publishedAt":19},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[91],{"categoryNo":53,"categoryName":54,"taxonomyType":55,"taxonomyTypeDesc":56},[88],{"skillNo":94,"name":95,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":96,"categories":97,"keywords":98,"publishedAt":19},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[95]]