Back to skills
SkillHub ClubShip Full StackFull Stack

graphql-schema-designer

Designs GraphQL schemas with type definitions, implements resolvers with TypeScript/Python, optimizes queries using DataLoader to prevent N+1 problems, sets up Apollo Federation for microservices, and implements real-time subscriptions. Use when building new GraphQL APIs, optimizing existing schemas, migrating from REST to GraphQL, implementing Apollo/Strawberry/GraphQL-Yoga, adding real-time features, or preventing query performance issues.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
1
Hot score
77
Updated
March 20, 2026
Overall rating
C2.9
Composite score
2.9
Best-practice grade
B78.7

Install command

npx @skill-hub/cli install wenis-rad-graphql-schema-designer

Repository

wenis/rad

Skill path: skills/graphql-schema-designer

Designs GraphQL schemas with type definitions, implements resolvers with TypeScript/Python, optimizes queries using DataLoader to prevent N+1 problems, sets up Apollo Federation for microservices, and implements real-time subscriptions. Use when building new GraphQL APIs, optimizing existing schemas, migrating from REST to GraphQL, implementing Apollo/Strawberry/GraphQL-Yoga, adding real-time features, or preventing query performance issues.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: wenis.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install graphql-schema-designer into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/wenis/rad before adding graphql-schema-designer to shared team environments
  • Use graphql-schema-designer for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: graphql-schema-designer
description: Designs GraphQL schemas with type definitions, implements resolvers with TypeScript/Python, optimizes queries using DataLoader to prevent N+1 problems, sets up Apollo Federation for microservices, and implements real-time subscriptions. Use when building new GraphQL APIs, optimizing existing schemas, migrating from REST to GraphQL, implementing Apollo/Strawberry/GraphQL-Yoga, adding real-time features, or preventing query performance issues.
allowed-tools: Read, Write, Edit, Bash
---

# GraphQL Schema Designer

You design type-safe, performant GraphQL schemas with proper resolver implementation, N+1 prevention, and federation support.

## When to use
- Building new GraphQL API
- Migrating from REST to GraphQL
- Optimizing existing GraphQL performance (N+1 problems)
- Implementing Apollo Federation (microservices)
- Adding subscriptions (real-time features)
- Setting up schema-first development

## GraphQL Basics

### Schema Definition Language (SDL)

GraphQL schemas define types, queries, and mutations:

```graphql
type User {
  id: ID!
  email: String!
  name: String!
  posts: [Post!]!
  createdAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  published: Boolean!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  post(id: ID!): Post
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
}

input CreateUserInput {
  email: String!
  name: String!
}

input UpdateUserInput {
  email: String
  name: String
}
```

**Key concepts:**
- `!` means required (non-nullable)
- `[Post!]!` means required list of required posts
- `input` types for mutations
- `Query` for reading data
- `Mutation` for writing data

### Basic Resolver Pattern

```typescript
const resolvers = {
  Query: {
    user: async (parent, { id }, context) => {
      return context.db.users.findById(id);
    },

    users: async (parent, { limit, offset }, context) => {
      return context.db.users.findMany({ limit, offset });
    },
  },

  Mutation: {
    createUser: async (parent, { input }, context) => {
      return context.db.users.create(input);
    },
  },

  User: {
    // Field resolver for User.posts
    posts: async (parent, args, context) => {
      // Use DataLoader to prevent N+1!
      return context.loaders.postsByAuthor.load(parent.id);
    },
  },

  Post: {
    // Field resolver for Post.author
    author: async (parent, args, context) => {
      return context.loaders.user.load(parent.authorId);
    },
  },
};
```

**Resolver signature:** `(parent, args, context, info) => result`
- `parent` - Result from parent resolver
- `args` - Arguments passed to field
- `context` - Shared context (db, loaders, user, etc.)
- `info` - Query metadata (rarely used)

## The N+1 Problem (Critical!)

**Problem:** Nested queries can trigger N+1 database queries:

```graphql
query {
  users {        # 1 query
    name
    posts {      # N queries (one per user!)
      title
    }
  }
}
```

**Solution:** Always use DataLoader!

```typescript
// dataloaders.ts
import DataLoader from 'dataloader';

export function createLoaders(db) {
  return {
    user: new DataLoader(async (ids) => {
      const users = await db.query(
        'SELECT * FROM users WHERE id IN (?)',
        [ids]
      );
      const userMap = new Map(users.map(u => [u.id, u]));
      return ids.map(id => userMap.get(id));
    }),

    postsByAuthor: new DataLoader(async (authorIds) => {
      const posts = await db.query(
        'SELECT * FROM posts WHERE author_id IN (?)',
        [authorIds]
      );
      // Group by author_id
      const grouped = authorIds.map(id =>
        posts.filter(p => p.author_id === id)
      );
      return grouped;
    }),
  };
}

// Add to context
context: async () => ({
  loaders: createLoaders(db),
}),
```

**For detailed DataLoader implementation, see:** `examples/n+1-prevention.md`

## Detailed Implementation Guides

For framework-specific implementations, see:

### Server Implementations
- **Apollo Server (TypeScript/Node.js):** `examples/apollo-server.md`
  - Complete setup with auth, data sources, testing
  - Code generation with graphql-codegen

- **Strawberry (Python):** `examples/strawberry-python.md`
  - FastAPI integration
  - Python DataLoader patterns

### Advanced Features
- **N+1 Prevention:** `examples/n+1-prevention.md`
  - Detailed DataLoader patterns
  - Batching strategies
  - Testing DataLoader

- **Apollo Federation:** `examples/federation.md`
  - Microservices architecture
  - Cross-service queries
  - Gateway setup

- **Pagination:** `examples/pagination.md`
  - Offset-based pagination
  - Cursor-based (Relay spec)
  - Implementation patterns

- **Subscriptions:** `examples/subscriptions.md`
  - Real-time updates with WebSockets
  - PubSub patterns
  - Subscription lifecycle

### Security & Best Practices
- **Security:** `reference/security.md`
  - Query complexity limiting
  - Query depth limiting
  - Rate limiting
  - Authentication patterns

## Instructions

When building a GraphQL API:

1. **Design schema first:**
   - Define types based on domain model
   - Create queries for reading
   - Create mutations for writing
   - Add inputs for mutation parameters

2. **Implement resolvers:**
   - Query resolvers fetch data
   - Mutation resolvers modify data
   - Field resolvers for computed/relational fields
   - **Always use DataLoader for relational fields**

3. **Prevent N+1 queries:**
   - Create DataLoaders for each entity type
   - Add loaders to context
   - Use loaders in field resolvers
   - Test that queries are batched

4. **Add security:**
   - Authenticate mutations
   - Authorize field access
   - Limit query complexity
   - Limit query depth
   - Add rate limiting

5. **Add pagination:**
   - Use offset-based for simple cases
   - Use cursor-based for production
   - Always return totalCount
   - Implement hasNextPage

6. **Test thoroughly:**
   - Unit test resolvers
   - Integration test queries/mutations
   - Test error cases
   - Test authentication/authorization

7. **Document schema:**
   - Add descriptions to types
   - Add descriptions to fields
   - Document inputs
   - Version breaking changes

## Common Patterns

### Authentication

```typescript
type Query {
  me: User  # Current authenticated user
}

type Mutation {
  login(email: String!, password: String!): AuthPayload!
}

type AuthPayload {
  token: String!
  user: User!
}
```

### Error Handling

```typescript
import { GraphQLError } from 'graphql';

user: async (parent, { id }, context) => {
  const user = await context.db.users.findById(id);
  if (!user) {
    throw new GraphQLError('User not found', {
      extensions: {
        code: 'USER_NOT_FOUND',
        id,
      },
    });
  }
  return user;
},
```

### Pagination Response

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

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

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

## Best Practices

✅ **DO:**
- Use DataLoader for N+1 prevention (critical!)
- Implement pagination for lists
- Add authentication/authorization
- Limit query complexity and depth
- Use strongly typed resolvers
- Document schema with descriptions
- Version breaking changes
- Handle errors with GraphQLError
- Use input types for mutations
- Add field-level authorization

❌ **DON'T:**
- Query in field resolvers without DataLoader
- Allow unlimited nesting
- Return null for errors (use errors array)
- Skip input validation
- Implement REST-style endpoints in GraphQL
- Forget to handle errors properly
- Expose internal IDs without encoding
- Allow unauthenticated mutations
- Skip pagination on lists
- Create N+1 queries

## Testing

```typescript
describe('User resolvers', () => {
  it('fetches user by ID', async () => {
    const query = `
      query GetUser($id: ID!) {
        user(id: $id) {
          id
          email
          name
        }
      }
    `;

    const result = await execute({
      schema,
      document: parse(query),
      variableValues: { id: '1' },
      contextValue: { db: mockDb, loaders: mockLoaders },
    });

    expect(result.data?.user).toEqual({
      id: '1',
      email: '[email protected]',
      name: 'Test User',
    });
  });
});
```

## Tools & Resources

**Development:**
- GraphQL Playground / GraphiQL for testing
- Apollo Sandbox for testing federated schemas
- graphql-codegen for TypeScript types

**Monitoring:**
- Apollo Studio for schema analytics
- Query complexity monitoring
- Performance tracing

**Libraries:**
- Node.js: `@apollo/server`, `dataloader`, `graphql`
- Python: `strawberry-graphql`, `ariadne`, `graphene`
- Go: `gqlgen`, `graphql-go`

## Constraints

- Must prevent N+1 queries (use DataLoader)
- Must limit query complexity
- Must authenticate mutations
- Must validate inputs
- Schema must be strongly typed
- Must handle errors gracefully
- Subscriptions must clean up on disconnect
- Must implement pagination for lists
- Must add field-level authorization where needed


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### examples/apollo-server.md

```markdown
# Apollo Server (Node.js/TypeScript) Implementation

Complete Apollo Server setup with TypeScript, including authentication and data sources.

## Installation

```bash
npm install @apollo/server graphql
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers
```

## Basic Server Setup

```typescript
// server.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs } from './schema';
import { resolvers } from './resolvers';

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
  context: async ({ req }) => ({
    user: await getUserFromToken(req.headers.authorization),
    dataSources: {
      usersAPI: new UsersAPI(),
      postsAPI: new PostsAPI(),
    },
  }),
});

console.log(`Server ready at ${url}`);
```

## Schema Definition

```typescript
// schema.ts
export const typeDefs = `#graphql
  type User {
    id: ID!
    email: String!
    name: String!
    posts: [Post!]!
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
    published: Boolean!
  }

  type Query {
    user(id: ID!): User
    users(limit: Int = 10, offset: Int = 0): [User!]!
    me: User
  }

  type Mutation {
    createUser(input: CreateUserInput!): User!
    createPost(input: CreatePostInput!): Post!
  }

  input CreateUserInput {
    email: String!
    name: String!
  }

  input CreatePostInput {
    title: String!
    content: String!
    published: Boolean = false
  }
`;
```

## Resolvers

```typescript
// resolvers.ts
import { GraphQLError } from 'graphql';

export const resolvers = {
  Query: {
    user: async (parent, { id }, context) => {
      const user = await context.dataSources.usersAPI.getUser(id);
      if (!user) {
        throw new GraphQLError('User not found', {
          extensions: { code: 'USER_NOT_FOUND' },
        });
      }
      return user;
    },

    users: async (parent, { limit, offset }, context) => {
      return context.dataSources.usersAPI.getUsers({ limit, offset });
    },

    me: async (parent, args, context) => {
      if (!context.user) {
        throw new GraphQLError('Not authenticated', {
          extensions: { code: 'UNAUTHENTICATED' },
        });
      }
      return context.user;
    },
  },

  Mutation: {
    createUser: async (parent, { input }, context) => {
      return context.dataSources.usersAPI.createUser(input);
    },

    createPost: async (parent, { input }, context) => {
      if (!context.user) {
        throw new GraphQLError('Not authenticated', {
          extensions: { code: 'UNAUTHENTICATED' },
        });
      }

      return context.dataSources.postsAPI.createPost({
        ...input,
        authorId: context.user.id,
      });
    },
  },

  User: {
    // Field resolver - called for User.posts
    posts: async (parent, args, context) => {
      return context.dataSources.postsAPI.getPostsByAuthor(parent.id);
    },
  },

  Post: {
    // Field resolver - called for Post.author
    author: async (parent, args, context) => {
      return context.dataSources.usersAPI.getUser(parent.authorId);
    },
  },
};
```

## TypeScript Types with Codegen

**codegen.yml:**
```yaml
schema: http://localhost:4000/graphql
generates:
  src/generated/graphql.ts:
    plugins:
      - typescript
      - typescript-resolvers
    config:
      contextType: ./context#Context
      mappers:
        User: ./models#UserModel
        Post: ./models#PostModel
```

**Run:**
```bash
npx graphql-codegen --config codegen.yml
```

## Testing

```typescript
// resolvers.test.ts
import { createMockServer } from '@graphql-tools/mock';
import { execute, parse } from 'graphql';

describe('User resolvers', () => {
  it('fetches user by ID', async () => {
    const query = `
      query GetUser($id: ID!) {
        user(id: $id) {
          id
          email
          name
        }
      }
    `;

    const result = await execute({
      schema,
      document: parse(query),
      variableValues: { id: '1' },
      contextValue: { dataSources: mockDataSources },
    });

    expect(result.data?.user).toEqual({
      id: '1',
      email: '[email protected]',
      name: 'Test User',
    });
  });
});
```

```

### examples/federation.md

```markdown
# Apollo Federation - Microservices

Apollo Federation allows you to split your GraphQL schema across multiple services.

## Architecture

```
Gateway (Unified Schema)
  ├── Users Service (users, auth)
  ├── Posts Service (posts, comments)
  └── Analytics Service (metrics, stats)
```

## Users Service

```typescript
// users-service/schema.ts
import { buildSubgraphSchema } from '@apollo/subgraph';

const typeDefs = `#graphql
  type User @key(fields: "id") {
    id: ID!
    email: String!
    name: String!
  }

  extend type Post @key(fields: "id") {
    id: ID! @external
    author: User
  }

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

const resolvers = {
  User: {
    __resolveReference: async (reference) => {
      return getUserById(reference.id);
    },
  },

  Post: {
    author: async (post) => {
      return getUserById(post.authorId);
    },
  },
};

export const schema = buildSubgraphSchema({ typeDefs, resolvers });
```

## Posts Service

```typescript
// posts-service/schema.ts
import { buildSubgraphSchema } from '@apollo/subgraph';

const typeDefs = `#graphql
  type Post @key(fields: "id") {
    id: ID!
    title: String!
    content: String!
    authorId: ID!
  }

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

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

const resolvers = {
  Post: {
    __resolveReference: async (reference) => {
      return getPostById(reference.id);
    },
  },

  User: {
    posts: async (user) => {
      return getPostsByAuthorId(user.id);
    },
  },
};

export const schema = buildSubgraphSchema({ typeDefs, resolvers });
```

## Gateway

```typescript
// gateway/server.ts
import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const gateway = new ApolloGateway({
  supergraphSdl: new IntrospectAndCompose({
    subgraphs: [
      { name: 'users', url: 'http://localhost:4001/graphql' },
      { name: 'posts', url: 'http://localhost:4002/graphql' },
    ],
  }),
});

const server = new ApolloServer({ gateway });

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`Gateway ready at ${url}`);
```

## Key Directives

- `@key(fields: "id")` - Defines entity that can be referenced across services
- `@external` - Field owned by another service
- `extend type` - Add fields to type from another service
- `__resolveReference` - Resolver for fetching entity by key

## Cross-Service Queries

Users can query across services seamlessly:

```graphql
query {
  user(id: "1") {     # Fetched from users-service
    id
    name
    posts {           # Fetched from posts-service
      title
      content
    }
  }
}
```

The gateway automatically:
1. Sends `user(id: "1")` to users-service
2. Gets back `{ id: "1", name: "..." }`
3. Sends `posts` query to posts-service with `user.id`
4. Combines results

## Best Practices

✅ **DO:**
- Define clear service boundaries
- Use `@key` on shared entities
- Implement `__resolveReference` for all entities
- Version breaking changes carefully

❌ **DON'T:**
- Create circular dependencies between services
- Expose internal IDs without encoding
- Skip authentication in subgraphs
- Forget to handle cross-service errors

```

graphql-schema-designer | SkillHub