Claude
Skills
Sign in
Back

graphql-expert

Included with Lifetime
$97 forever

GraphQL API design and implementation. Use when building GraphQL APIs, designing schemas, implementing resolvers, or optimizing GraphQL performance.

Design

What this skill does


# GraphQL Expert

Comprehensive guide for designing and implementing GraphQL APIs.

## GraphQL Fundamentals

### What is GraphQL?

```
GraphQL is a query language for APIs that:
✓ Lets clients request exactly what they need
✓ Gets multiple resources in one request
✓ Uses a type system to describe data
✓ Provides introspection (self-documenting)

GraphQL vs REST:
┌─────────────────────────────────────────┐
│ REST: Multiple endpoints, fixed shapes  │
│ GET /users/1                            │
│ GET /users/1/posts                      │
│ GET /users/1/followers                  │
├─────────────────────────────────────────┤
│ GraphQL: Single endpoint, flexible      │
│ POST /graphql                           │
│ query { user(id: 1) {                   │
│   name                                  │
│   posts { title }                       │
│   followers { name }                    │
│ }}                                      │
└─────────────────────────────────────────┘
```

---

## Schema Design

### Type System

```graphql
# Scalar Types (built-in)
String, Int, Float, Boolean, ID

# Custom Scalar
scalar DateTime
scalar JSON

# Object Type
type User {
  id: ID!
  email: String!
  name: String
  createdAt: DateTime!
  posts: [Post!]!
}

# Enum
enum Role {
  ADMIN
  USER
  GUEST
}

# Interface
interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  # ... other fields
}

# Union
union SearchResult = User | Post | Comment

# Input Type (for mutations)
input CreateUserInput {
  email: String!
  name: String!
  role: Role = USER
}
```

### Nullability

```graphql
# Field modifiers:
String      # Nullable string
String!     # Non-null string
[String]    # Nullable list of nullable strings
[String!]   # Nullable list of non-null strings
[String]!   # Non-null list of nullable strings
[String!]!  # Non-null list of non-null strings

# Best practice:
# - Make fields nullable by default
# - Use ! only when guaranteed non-null
# - Lists should usually be non-null: [Item!]!
```

### Schema Structure

```graphql
# Root Types
type Query {
  # Read operations
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  me: User
}

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

type Subscription {
  # Real-time updates
  userCreated: User!
  messageReceived(roomId: ID!): Message!
}
```

---

## Query Design

### Basic Queries

```graphql
# Simple query
query GetUser {
  user(id: "1") {
    name
    email
  }
}

# With variables
query GetUser($id: ID!) {
  user(id: $id) {
    name
    email
  }
}

# Multiple queries
query Dashboard {
  me {
    name
    notifications {
      count
    }
  }
  recentPosts(limit: 5) {
    title
    createdAt
  }
}
```

### Pagination

```graphql
# Offset-based (simple, but has issues)
type Query {
  users(limit: Int!, offset: Int!): [User!]!
}

# Cursor-based (recommended)
type Query {
  users(first: Int, after: String): UserConnection!
}

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

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

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

# Usage
query {
  users(first: 10, after: "cursor123") {
    edges {
      cursor
      node {
        name
        email
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

### Filtering & Sorting

```graphql
input UserFilter {
  name: StringFilter
  email: StringFilter
  role: Role
  createdAt: DateFilter
}

input StringFilter {
  equals: String
  contains: String
  startsWith: String
}

input DateFilter {
  before: DateTime
  after: DateTime
}

enum UserSortField {
  NAME
  EMAIL
  CREATED_AT
}

input UserSort {
  field: UserSortField!
  direction: SortDirection!
}

enum SortDirection {
  ASC
  DESC
}

type Query {
  users(
    filter: UserFilter
    sort: UserSort
    first: Int
    after: String
  ): UserConnection!
}
```

---

## Mutations

### Mutation Design

```graphql
# Input types for mutations
input CreatePostInput {
  title: String!
  content: String!
  published: Boolean = false
  categoryIds: [ID!]
}

input UpdatePostInput {
  title: String
  content: String
  published: Boolean
}

# Mutation payloads (recommended)
type CreatePostPayload {
  post: Post
  errors: [Error!]!
}

type Error {
  field: String
  message: String!
}

type Mutation {
  createPost(input: CreatePostInput!): CreatePostPayload!
  updatePost(id: ID!, input: UpdatePostInput!): UpdatePostPayload!
  deletePost(id: ID!): DeletePostPayload!
}

# Usage
mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    post {
      id
      title
    }
    errors {
      field
      message
    }
  }
}
```

### Batch Mutations

```graphql
# For multiple operations
type Mutation {
  bulkDeletePosts(ids: [ID!]!): BulkDeletePayload!
  bulkUpdatePosts(updates: [PostUpdate!]!): BulkUpdatePayload!
}

input PostUpdate {
  id: ID!
  input: UpdatePostInput!
}
```

---

## Resolvers

### Basic Resolvers

```typescript
// TypeScript resolver implementation
const resolvers = {
  Query: {
    user: async (_, { id }, context) => {
      return context.dataSources.users.findById(id);
    },

    users: async (_, { filter, sort, first, after }, context) => {
      return context.dataSources.users.findMany({
        filter,
        sort,
        first,
        after,
      });
    },
  },

  Mutation: {
    createUser: async (_, { input }, context) => {
      // Validate
      const errors = validateCreateUser(input);
      if (errors.length) {
        return { user: null, errors };
      }

      // Create
      const user = await context.dataSources.users.create(input);
      return { user, errors: [] };
    },
  },

  // Field resolvers
  User: {
    posts: async (user, _, context) => {
      return context.dataSources.posts.findByUserId(user.id);
    },

    fullName: (user) => {
      return `${user.firstName} ${user.lastName}`;
    },
  },
};
```

### Resolver Context

```typescript
// Context setup
interface Context {
  user: User | null;
  dataSources: {
    users: UserDataSource;
    posts: PostDataSource;
  };
  loaders: {
    userLoader: DataLoader<string, User>;
    postLoader: DataLoader<string, Post>;
  };
}

// In server setup
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }) => ({
    user: await getUserFromToken(req.headers.authorization),
    dataSources: {
      users: new UserDataSource(db),
      posts: new PostDataSource(db),
    },
    loaders: createLoaders(),
  }),
});
```

---

## Performance Optimization

### DataLoader (N+1 Solution)

```typescript
import DataLoader from "dataloader";

// Create loader
const userLoader = new DataLoader<string, User>(async (ids) => {
  const users = await db.users.findMany({
    where: { id: { in: ids } },
  });

  // Return in same order as input ids
  const userMap = new Map(users.map((u) => [u.id, u]));
  return ids.map((id) => userMap.get(id) || null);
});

// Use in resolver
const resolvers = {
  Post: {
    author: (post, _, context) => {
      return context.loaders.userLoader.load(post.authorId);
    },
  },
};
```

### Query Complexity

```typescript
import { createComplexityLimitRule } from "graphql-validation-complexity";

// Limit query complexity
const complexityLimitRule = createComplexityLimitRule(1000, {
  scalarCost: 1,
  objectCost: 10,
  listFactor: 10,
});

// Or field-level costs
const typeDefs = gql`
  type Query {
    users: [User!]! @complexity(value: 10, multipliers: ["first"])
    user(id: ID!): User @complexity(value: 1)
  }
`;
```

### Query Depth Limiting

```typescript
import depthLimit from "graphql-depth-limit";

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(10)],
});
```

### Persisted Queries

```typescript
// Client sends hash instead of 
Files: 1
Size: 14.5 KB
Complexity: 18/100
Category: Design

Related in Design