home / skills / aj-geddes / useful-ai-prompts / graphql-implementation

graphql-implementation skill

/skills/graphql-implementation

This skill helps you design and implement GraphQL APIs with scalable schemas, resolvers, and modern best practices.

npx playbooks add skill aj-geddes/useful-ai-prompts --skill graphql-implementation

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
8.1 KB
---
name: graphql-implementation
description: Design and implement GraphQL APIs with schema design, resolvers, queries, mutations, subscriptions, and best practices. Use when building GraphQL servers, designing schemas, or migrating from REST to GraphQL.
---

# GraphQL Implementation

## Overview

Implement GraphQL APIs with proper schema design, resolver patterns, error handling, and performance optimization for flexible client-server communication.

## When to Use

- Designing new GraphQL APIs
- Creating GraphQL schemas and types
- Implementing resolvers and mutations
- Adding subscriptions for real-time data
- Migrating from REST to GraphQL
- Optimizing GraphQL performance

## Instructions

### 1. **GraphQL Schema Design**

```graphql
type User {
  id: ID!
  email: String!
  firstName: String!
  lastName: String!
  role: UserRole!
  posts: [Post!]!
  createdAt: DateTime!
  updatedAt: DateTime!
}

enum UserRole {
  ADMIN
  USER
  MODERATOR
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
  publishedAt: DateTime
  createdAt: DateTime!
}

type Comment {
  id: ID!
  text: String!
  author: User!
  post: Post!
  createdAt: DateTime!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  post(id: ID!): Post
  posts(authorId: ID, limit: Int, offset: Int): [Post!]!
  search(query: String!): [SearchResult!]!
}

union SearchResult = User | Post | Comment

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
  createPost(input: CreatePostInput!): Post!
  updatePost(id: ID!, input: UpdatePostInput!): Post!
  deletePost(id: ID!): Boolean!
  createComment(postId: ID!, text: String!): Comment!
}

input CreateUserInput {
  email: String!
  firstName: String!
  lastName: String!
  role: UserRole!
}

input UpdateUserInput {
  email: String
  firstName: String
  lastName: String
  role: UserRole
}

input CreatePostInput {
  title: String!
  content: String!
}

input UpdatePostInput {
  title: String
  content: String
  publishedAt: DateTime
}

type Subscription {
  userCreated: User!
  postPublished: Post!
  commentAdded(postId: ID!): Comment!
}

scalar DateTime
```

### 2. **Node.js Apollo Server Implementation**

```javascript
const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');

const typeDefs = gql`
  type Query {
    user(id: ID!): User
    users: [User!]!
  }

  type User {
    id: ID!
    email: String!
    firstName: String!
    lastName: String!
    posts: [Post!]!
  }

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

  type Mutation {
    createUser(email: String!, firstName: String!, lastName: String!): User!
    createPost(title: String!, content: String!): Post!
  }
`;

const resolvers = {
  Query: {
    user: async (_, { id }, { db }) => {
      return db.users.findById(id);
    },
    users: async (_, __, { db }) => {
      return db.users.findAll();
    }
  },

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

  Post: {
    author: async (post, _, { db }) => {
      return db.users.findById(post.authorId);
    }
  },

  Mutation: {
    createUser: async (_, { email, firstName, lastName }, { db }) => {
      const user = { id: Date.now().toString(), email, firstName, lastName };
      db.users.save(user);
      return user;
    },
    createPost: async (_, { title, content }, { user, db }) => {
      if (!user) throw new Error('Unauthorized');
      const post = { id: Date.now().toString(), title, content, authorId: user.id };
      db.posts.save(post);
      return post;
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => ({
    user: req.user,
    db: require('./database')
  })
});

const app = express();
server.start().then(() => {
  server.applyMiddleware({ app });
  app.listen(4000, () => console.log('GraphQL server running on port 4000'));
});
```

### 3. **Python GraphQL Implementation (Graphene)**

```python
import graphene
from datetime import datetime
from typing import List

class UserType(graphene.ObjectType):
    id = graphene.ID(required=True)
    email = graphene.String(required=True)
    first_name = graphene.String(required=True)
    last_name = graphene.String(required=True)
    posts = graphene.List(lambda: PostType)

class PostType(graphene.ObjectType):
    id = graphene.ID(required=True)
    title = graphene.String(required=True)
    content = graphene.String(required=True)
    author = graphene.Field(UserType)
    created_at = graphene.DateTime(required=True)

class Query(graphene.ObjectType):
    user = graphene.Field(UserType, id=graphene.ID(required=True))
    users = graphene.List(UserType)
    posts = graphene.List(PostType, author_id=graphene.ID())

    def resolve_user(self, info, id):
        return User.objects.get(pk=id)

    def resolve_users(self, info):
        return User.objects.all()

    def resolve_posts(self, info, author_id=None):
        if author_id:
            return Post.objects.filter(author_id=author_id)
        return Post.objects.all()

class CreateUserMutation(graphene.Mutation):
    class Arguments:
        email = graphene.String(required=True)
        first_name = graphene.String(required=True)
        last_name = graphene.String(required=True)

    user = graphene.Field(UserType)
    success = graphene.Boolean()

    def mutate(self, info, email, first_name, last_name):
        user = User.objects.create(
            email=email,
            first_name=first_name,
            last_name=last_name
        )
        return CreateUserMutation(user=user, success=True)

class Mutation(graphene.ObjectType):
    create_user = CreateUserMutation.Field()

schema = graphene.Schema(query=Query, mutation=Mutation)
```

### 4. **Query Examples**

```graphql
# Get user with posts
query GetUserWithPosts {
  user(id: "123") {
    id
    email
    firstName
    posts {
      id
      title
      createdAt
    }
  }
}

# Paginated users query
query GetUsers($limit: Int, $offset: Int) {
  users(limit: $limit, offset: $offset) {
    id
    email
    firstName
  }
}

# Search across types
query Search($query: String!) {
  search(query: $query) {
    ... on User {
      id
      email
    }
    ... on Post {
      id
      title
    }
  }
}

# Create user mutation
mutation CreateUser($input: CreateUserInput!) {
  createUser(input: $input) {
    id
    email
    firstName
  }
}

# Subscribe to new comments
subscription OnCommentAdded($postId: ID!) {
  commentAdded(postId: $postId) {
    id
    text
    author {
      firstName
    }
  }
}
```

### 5. **Error Handling**

```javascript
const resolvers = {
  Query: {
    user: async (_, { id }) => {
      try {
        const user = await User.findById(id);
        if (!user) {
          throw new GraphQLError('User not found', {
            extensions: {
              code: 'NOT_FOUND',
              userId: id
            }
          });
        }
        return user;
      } catch (error) {
        throw new GraphQLError('Database error', {
          originalError: error,
          extensions: { code: 'INTERNAL_ERROR' }
        });
      }
    }
  }
};

server.formatError = (formattedError) => ({
  message: formattedError.message,
  code: formattedError.extensions?.code || 'INTERNAL_ERROR',
  timestamp: new Date().toISOString()
});
```

## Best Practices

### ✅ DO
- Use clear, descriptive field names
- Design schemas around client needs
- Implement proper error handling
- Use input types for mutations
- Add subscriptions for real-time data
- Cache resolvers efficiently
- Validate input data
- Use federation for scalability

### ❌ DON'T
- Over-nest queries deeply
- Expose internal database IDs
- Return sensitive data without authorization
- Create overly complex schemas
- Forget to handle null values
- Ignore N+1 query problems
- Skip error messages

## Performance Tips

- Use DataLoader to batch database queries
- Implement query complexity analysis
- Cache at resolver level
- Use connection patterns for pagination
- Monitor query execution time

Overview

This skill helps design and implement production-ready GraphQL APIs, covering schema design, resolvers, queries, mutations, subscriptions, error handling, and performance optimizations. It provides concrete patterns and examples for Node.js (Apollo) and Python (Graphene) implementations to accelerate building scalable, maintainable GraphQL servers.

How this skill works

The skill inspects API requirements and translates them into a typed GraphQL schema with queries, mutations, unions, enums, input types, and custom scalars. It supplies resolver patterns, authentication/authorization checks, error formatting, subscription wiring for real-time updates, and performance techniques like DataLoader batching and query complexity limits.

When to use it

  • Designing a new API to replace or complement REST endpoints
  • Creating schemas, input types, and resolver patterns for CRUD operations
  • Adding subscriptions or real-time features to an existing service
  • Migrating a REST service to GraphQL while preserving performance
  • Optimizing resolver performance and preventing N+1 queries

Best practices

  • Design schemas around client needs and use clear, descriptive field names
  • Use input types for mutations and validate inputs on arrival
  • Implement centralized error handling with GraphQL error extensions and consistent codes
  • Batch and cache database calls with DataLoader to avoid N+1 problems
  • Limit query complexity, paginate with connections, and monitor execution times

Example use cases

  • Build a user-post-comment API with queries, mutations, and subscriptions for new comments
  • Migrate multiple REST endpoints into a single GraphQL schema to reduce round trips
  • Add role-based authorization checks in mutation resolvers (e.g., ADMIN vs USER)
  • Implement search union types that return Users, Posts, or Comments in one query
  • Optimize resolver performance using DataLoader and resolver-level caching

FAQ

How do I prevent N+1 queries?

Use DataLoader to batch and cache related database fetches inside resolvers, and prefer joins or optimized queries when possible.

When should I use subscriptions?

Use subscriptions for real-time features like live comments or notifications; otherwise prefer queries and polling to reduce complexity.