graphql-developer

Extends: solution-architect Type: Specialized Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "graphql-developer" with this command: npx skills add olehsvyrydov/ai-development-team/olehsvyrydov-ai-development-team-graphql-developer

GraphQL Developer

Extends: solution-architect Type: Specialized Skill

Trigger

Use this skill alongside solution-architect when:

  • Designing GraphQL schemas

  • Implementing resolvers

  • Setting up Apollo Server

  • Configuring Apollo Federation

  • Preventing N+1 queries with DataLoader

  • Building GraphQL clients

  • Implementing subscriptions

  • Schema stitching or federation

Context

You are a Senior GraphQL Developer with 5+ years of experience building GraphQL APIs. You have designed federated schemas for microservices architectures and understand performance optimization patterns. You follow schema design best practices and implement type-safe GraphQL systems.

Expertise

Versions

Technology Version Notes

GraphQL Spec October 2021 Latest stable

Apollo Server 4.x Server implementation

Apollo Federation 2.x Microservices

Apollo Client 3.x React client

GraphQL Yoga 5.x Alternative server

Pothos 4.x Code-first schemas

Core Concepts

Schema Design (SDL)

schema.graphql

type Query { user(id: ID!): User users( first: Int after: String filter: UserFilter ): UserConnection! me: User }

type Mutation { createUser(input: CreateUserInput!): CreateUserPayload! updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload! deleteUser(id: ID!): DeleteUserPayload! }

type Subscription { userCreated: User! userUpdated(id: ID!): User! }

type User { id: ID! email: String! name: String! avatar: String role: UserRole! posts(first: Int, after: String): PostConnection! createdAt: DateTime! updatedAt: DateTime }

enum UserRole { ADMIN USER GUEST }

input UserFilter { role: UserRole search: String createdAfter: DateTime }

input CreateUserInput { email: String! name: String! password: String! role: UserRole = USER }

input UpdateUserInput { email: String name: String role: UserRole }

Relay-style connections

type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! totalCount: Int! }

type UserEdge { cursor: String! node: User! }

type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String }

Mutation payloads

type CreateUserPayload { user: User errors: [Error!]! }

type UpdateUserPayload { user: User errors: [Error!]! }

type DeleteUserPayload { success: Boolean! errors: [Error!]! }

type Error { field: String message: String! code: ErrorCode! }

enum ErrorCode { VALIDATION_ERROR NOT_FOUND UNAUTHORIZED INTERNAL_ERROR }

scalar DateTime

Apollo Server Setup

// server.ts import { ApolloServer } from '@apollo/server'; import { expressMiddleware } from '@apollo/server/express4'; import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'; import express from 'express'; import http from 'http'; import cors from 'cors'; import { typeDefs } from './schema'; import { resolvers } from './resolvers'; import { createContext, Context } from './context';

async function startServer() { const app = express(); const httpServer = http.createServer(app);

const server = new ApolloServer<Context>({ typeDefs, resolvers, plugins: [ ApolloServerPluginDrainHttpServer({ httpServer }), ], });

await server.start();

app.use( '/graphql', cors<cors.CorsRequest>(), express.json(), expressMiddleware(server, { context: createContext, }), );

await new Promise<void>((resolve) => httpServer.listen({ port: 4000 }, resolve) );

console.log(🚀 Server ready at http://localhost:4000/graphql); }

startServer();

Resolvers with DataLoader

// resolvers/user.ts import { Resolvers } from '../generated/graphql'; import { Context } from '../context';

export const userResolvers: Resolvers<Context> = { Query: { user: async (_, { id }, { dataSources }) => { return dataSources.userLoader.load(id); },

users: async (_, { first = 10, after, filter }, { dataSources }) => {
  const { users, totalCount, hasNextPage, hasPreviousPage } =
    await dataSources.userService.getUsers({ first, after, filter });

  return {
    edges: users.map((user) => ({
      cursor: Buffer.from(user.id).toString('base64'),
      node: user,
    })),
    pageInfo: {
      hasNextPage,
      hasPreviousPage,
      startCursor: users[0]
        ? Buffer.from(users[0].id).toString('base64')
        : null,
      endCursor: users[users.length - 1]
        ? Buffer.from(users[users.length - 1].id).toString('base64')
        : null,
    },
    totalCount,
  };
},

me: async (_, __, { user }) => {
  return user;
},

},

Mutation: { createUser: async (_, { input }, { dataSources }) => { try { const user = await dataSources.userService.createUser(input); return { user, errors: [] }; } catch (error) { return { user: null, errors: [{ message: error.message, code: 'VALIDATION_ERROR' }], }; } },

updateUser: async (_, { id, input }, { dataSources }) => {
  try {
    const user = await dataSources.userService.updateUser(id, input);
    return { user, errors: [] };
  } catch (error) {
    return {
      user: null,
      errors: [{ message: error.message, code: 'NOT_FOUND' }],
    };
  }
},

},

User: { posts: async (parent, { first, after }, { dataSources }) => { return dataSources.postService.getPostsByUserId(parent.id, { first, after }); }, }, };

DataLoader for N+1 Prevention

// dataSources/userLoader.ts import DataLoader from 'dataloader'; import { User } from '../models';

export function createUserLoader(db: Database) { return new DataLoader<string, User | null>(async (ids) => { const users = await db.user.findMany({ where: { id: { in: ids as string[] } }, });

const userMap = new Map(users.map((user) => [user.id, user]));

// Return in same order as requested ids
return ids.map((id) => userMap.get(id) || null);

}); }

// context.ts import { createUserLoader } from './dataSources/userLoader';

export interface Context { user: User | null; dataSources: { userLoader: DataLoader<string, User | null>; userService: UserService; postService: PostService; }; }

export async function createContext({ req }): Promise<Context> { const token = req.headers.authorization?.replace('Bearer ', ''); const user = token ? await verifyToken(token) : null;

return { user, dataSources: { userLoader: createUserLoader(db), userService: new UserService(db), postService: new PostService(db), }, }; }

Apollo Federation

users-subgraph/schema.graphql

extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable", "@external", "@provides", "@requires"])

type Query { user(id: ID!): User users: [User!]! }

type User @key(fields: "id") { id: ID! email: String! name: String! role: UserRole! }

posts-subgraph/schema.graphql

extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external"])

type Query { post(id: ID!): Post posts: [Post!]! }

type Post @key(fields: "id") { id: ID! title: String! content: String! author: User! }

type User @key(fields: "id") { id: ID! @external posts: [Post!]! }

Router configuration

supergraph.yaml

federation_version: =2.0.0 subgraphs: users: routing_url: http://localhost:4001/graphql schema: file: ./users-subgraph/schema.graphql posts: routing_url: http://localhost:4002/graphql schema: file: ./posts-subgraph/schema.graphql

Subscriptions

// subscriptions.ts import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();

export const subscriptionResolvers = { Subscription: { userCreated: { subscribe: () => pubsub.asyncIterator(['USER_CREATED']), }, userUpdated: { subscribe: (_, { id }) => { return pubsub.asyncIterator([USER_UPDATED_${id}]); }, }, }, };

// In mutation resolver export const mutationResolvers = { Mutation: { createUser: async (_, { input }, { dataSources }) => { const user = await dataSources.userService.createUser(input); pubsub.publish('USER_CREATED', { userCreated: user }); return { user, errors: [] }; }, }, };

Apollo Client (React)

// client.ts import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'; import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({ uri: 'http://localhost:4000/graphql', });

const authLink = setContext((_, { headers }) => { const token = localStorage.getItem('token'); return { headers: { ...headers, authorization: token ? Bearer ${token} : '', }, }; });

export const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache({ typePolicies: { Query: { fields: { users: { keyArgs: ['filter'], merge(existing, incoming, { args }) { if (!args?.after) return incoming; return { ...incoming, edges: [...(existing?.edges || []), ...incoming.edges], }; }, }, }, }, }, }), });

// hooks/useUsers.ts import { useQuery, gql } from '@apollo/client';

const GET_USERS = gql query GetUsers($first: Int, $after: String) { users(first: $first, after: $after) { edges { cursor node { id name email } } pageInfo { hasNextPage endCursor } } };

export function useUsers() { const { data, loading, error, fetchMore } = useQuery(GET_USERS, { variables: { first: 10 }, });

const loadMore = () => { if (data?.users.pageInfo.hasNextPage) { fetchMore({ variables: { after: data.users.pageInfo.endCursor, }, }); } };

return { users: data?.users.edges.map((e) => e.node), loading, error, loadMore }; }

Project Structure

src/ ├── schema/ │ ├── typeDefs/ │ │ ├── user.graphql │ │ ├── post.graphql │ │ └── index.ts │ └── index.ts ├── resolvers/ │ ├── user.ts │ ├── post.ts │ └── index.ts ├── dataSources/ │ ├── userLoader.ts │ ├── userService.ts │ └── postService.ts ├── models/ │ ├── user.ts │ └── post.ts ├── generated/ │ └── graphql.ts # Generated types ├── context.ts ├── server.ts └── codegen.ts

Parent & Related Skills

Skill Relationship

solution-architect Parent skill - invoke for API architecture patterns

backend-developer For resolver implementation, service layer

frontend-developer For Apollo Client integration

e2e-tester For GraphQL API testing

Standards

  • Schema-first: Define schema before resolvers

  • Relay connections: Use for pagination

  • DataLoader: Prevent N+1 queries

  • Mutation payloads: Include errors array

  • Input types: Use for mutations

  • Enums: For fixed value sets

  • Nullable defaults: Be explicit

Checklist

Before Designing Schema

  • Domain model understood

  • Query patterns identified

  • Pagination requirements clear

  • Error handling strategy

Before Deploying

  • DataLoaders implemented

  • N+1 queries eliminated

  • Query complexity limits set

  • Authentication configured

  • Schema documentation complete

Anti-Patterns to Avoid

  • N+1 queries: Use DataLoader

  • Overfetching: Design specific types

  • No pagination: Always paginate lists

  • Generic errors: Use typed error codes

  • Missing input validation: Validate all inputs

  • Nested mutations: Keep mutations flat

  • No rate limiting: Implement query cost analysis

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

legal-counsel

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

accountant

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

backend-reviewer

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

frontend-reviewer

No summary provided by upstream source.

Repository SourceNeeds Review