Claude
Skills
Sign in
Back

graphql-api-development

Included with Lifetime
$97 forever

Comprehensive guide for building GraphQL APIs including schema design, queries, mutations, subscriptions, resolvers, type system, error handling, authentication, authorization, caching strategies, and production best practices

Designgraphqlapischemaqueriesmutationssubscriptionsresolversjavascript

What this skill does


# GraphQL API Development

A comprehensive skill for building production-ready GraphQL APIs using graphql-js. Master schema design, type systems, resolvers, queries, mutations, subscriptions, authentication, authorization, caching, testing, and deployment strategies.

## When to Use This Skill

Use this skill when:

- Building a new API that requires flexible data fetching for web or mobile clients
- Replacing or augmenting REST APIs with more efficient data access patterns
- Developing APIs for applications with complex, nested data relationships
- Creating APIs that serve multiple client types (web, mobile, desktop) with different data needs
- Building real-time applications requiring subscriptions and live updates
- Designing APIs where clients need to specify exactly what data they need
- Developing GraphQL servers with Node.js and Express
- Implementing type-safe APIs with strong schema validation
- Creating self-documenting APIs with built-in introspection
- Building microservices that need to be composed into a unified API

## When GraphQL Excels Over REST

### GraphQL Advantages

1. **Precise Data Fetching**: Clients request exactly what they need, no over/under-fetching
2. **Single Request**: Fetch multiple resources in one roundtrip instead of multiple REST endpoints
3. **Strongly Typed**: Schema defines exact types, enabling validation and tooling
4. **Introspection**: Self-documenting API with queryable schema
5. **Versioning Not Required**: Add new fields without breaking existing queries
6. **Real-time Updates**: Built-in subscription support for live data
7. **Nested Resources**: Naturally handle complex relationships without N+1 queries
8. **Client-Driven**: Clients control data shape, reducing backend changes

### When to Stick with REST

- Simple CRUD operations with standard resources
- File uploads/downloads (GraphQL requires multipart handling)
- HTTP caching is critical (GraphQL typically uses POST)
- Team unfamiliar with GraphQL (learning curve)
- Existing REST infrastructure works well

## Core Concepts

### The GraphQL Type System

GraphQL's type system is its foundation. Every GraphQL API defines:

1. **Scalar Types**: Basic data types (String, Int, Float, Boolean, ID)
2. **Object Types**: Complex types with fields
3. **Query Type**: Entry point for read operations
4. **Mutation Type**: Entry point for write operations
5. **Subscription Type**: Entry point for real-time updates
6. **Input Types**: Complex inputs for mutations
7. **Enums**: Fixed set of values
8. **Interfaces**: Abstract types that objects implement
9. **Unions**: Types that can be one of several types
10. **Non-Null Types**: Types that cannot be null
11. **List Types**: Arrays of types

### Schema Definition

Two approaches for defining GraphQL schemas:

**1. Schema Definition Language (SDL)** - Declarative, readable:

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

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

type Query {
  user(id: ID!): User
  posts: [Post!]!
}
```

**2. Programmatic API** - Type-safe, programmatic:

```javascript
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: new GraphQLNonNull(GraphQLID) },
    name: { type: new GraphQLNonNull(GraphQLString) },
    email: { type: new GraphQLNonNull(GraphQLString) },
    posts: { type: new GraphQLList(new GraphQLNonNull(PostType)) }
  }
});
```

### Resolvers

Resolvers are functions that return data for schema fields. Every field can have a resolver:

```javascript
const resolvers = {
  Query: {
    user: (parent, args, context, info) => {
      return context.db.findUserById(args.id);
    }
  },
  User: {
    posts: (user, args, context) => {
      return context.db.findPostsByAuthorId(user.id);
    }
  }
};
```

**Resolver Function Signature**:
- `parent`: The result from the parent resolver
- `args`: Arguments passed to the field
- `context`: Shared context (database, auth, etc.)
- `info`: Field-specific metadata

### Queries

Queries fetch data from your API:

```graphql
query GetUser {
  user(id: "123") {
    id
    name
    email
    posts {
      title
      content
    }
  }
}
```

### Mutations

Mutations modify data:

```graphql
mutation CreatePost {
  createPost(input: {
    title: "GraphQL is awesome"
    content: "Here's why..."
    authorId: "123"
  }) {
    id
    title
    author {
      name
    }
  }
}
```

### Subscriptions

Subscriptions enable real-time updates:

```graphql
subscription OnPostCreated {
  postCreated {
    id
    title
    author {
      name
    }
  }
}
```

## Schema Design Patterns

### Pattern 1: Input Types for Mutations

Always use input types for complex mutation arguments:

```graphql
input CreateUserInput {
  name: String!
  email: String!
  age: Int
  bio: String
}

type Mutation {
  createUser(input: CreateUserInput!): User!
}
```

**Why**: Easier to extend, better organization, reusable across mutations.

### Pattern 2: Interfaces for Shared Fields

Use interfaces when multiple types share fields:

```graphql
interface Node {
  id: ID!
  createdAt: String!
  updatedAt: String!
}

type User implements Node {
  id: ID!
  createdAt: String!
  updatedAt: String!
  name: String!
  email: String!
}

type Post implements Node {
  id: ID!
  createdAt: String!
  updatedAt: String!
  title: String!
  content: String
}
```

### Pattern 3: Unions for Polymorphic Returns

Use unions when a field can return different types:

```graphql
union SearchResult = User | Post | Comment

type Query {
  search(query: String!): [SearchResult!]!
}
```

### Pattern 4: Pagination Patterns

**Offset-based pagination**:

```graphql
type Query {
  posts(offset: Int, limit: Int): PostConnection!
}

type PostConnection {
  items: [Post!]!
  total: Int!
  hasMore: Boolean!
}
```

**Cursor-based pagination (Relay-style)**:

```graphql
type Query {
  posts(first: Int, after: String): PostConnection!
}

type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
}

type PostEdge {
  node: Post!
  cursor: String!
}

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

### Pattern 5: Error Handling

**Field-level errors**:

```graphql
type MutationPayload {
  success: Boolean!
  message: String
  user: User
  errors: [Error!]
}

type Error {
  field: String!
  message: String!
}
```

**Union-based error handling**:

```graphql
union CreateUserResult = User | ValidationError | DatabaseError

type ValidationError {
  field: String!
  message: String!
}
```

### Pattern 6: Versioning with Directives

Deprecate fields instead of versioning:

```graphql
type User {
  name: String! @deprecated(reason: "Use firstName and lastName")
  firstName: String!
  lastName: String!
}
```

## Query Optimization and Performance

### The N+1 Problem

**Problem**: Fetching nested data causes multiple database queries:

```javascript
// BAD: N+1 queries
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    posts: {
      type: new GraphQLList(PostType),
      resolve: (user) => {
        // This runs once PER user!
        return db.getPostsByUserId(user.id);
      }
    }
  }
});

// Query for 100 users = 1 query for users + 100 queries for posts = 101 queries
```

### DataLoader Solution

DataLoader batches and caches requests:

```javascript
import DataLoader from 'dataloader';

// Create DataLoader
const postLoader = new DataLoader(async (userIds) => {
  // Single query for all user IDs
  const posts = await db.getPostsByUserIds(userIds);

  // Group posts by userId
  const postsByUserId = {};
  posts.forEach(post => {
    if (!postsByUserId[post.authorId]) {
      postsByUserId[post.authorId] = [];
    }
    postsByUserId[post.authorId].push(post);
  });

  // Return in same order as userIds
  return userIds.map(id => postsByUserId[id] || []);
});

// Use in resolver
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    posts: {
      type: new

Related in Design