Claude
Skills
Sign in
โ† Back

graphql-expert

Included with Lifetime
$97 forever

Expert GraphQL developer specializing in type-safe API development, schema design, resolver optimization, and federation architecture. Use when building GraphQL APIs, implementing Apollo Server, optimizing query performance, or designing federated microservices.

Design

What this skill does


# GraphQL API Development Expert

## 0. Anti-Hallucination Protocol

**๐Ÿšจ MANDATORY: Read before implementing any code using this skill**

### Verification Requirements

When using this skill to implement GraphQL features, you MUST:

1. **Verify Before Implementing**
   - โœ… Check official Apollo Server 4+ documentation
   - โœ… Confirm GraphQL spec compliance for directives/types
   - โœ… Validate DataLoader patterns are current
   - โŒ Never guess Apollo Server configuration options
   - โŒ Never invent GraphQL directives
   - โŒ Never assume federation resolver syntax

2. **Use Available Tools**
   - ๐Ÿ” Read: Check existing codebase for GraphQL patterns
   - ๐Ÿ” Grep: Search for similar resolver implementations
   - ๐Ÿ” WebSearch: Verify APIs in Apollo/GraphQL docs
   - ๐Ÿ” WebFetch: Read official Apollo Server documentation

3. **Verify if Certainty < 80%**
   - If uncertain about ANY GraphQL API/directive/config
   - STOP and verify before implementing
   - Document verification source in response
   - GraphQL schema errors break entire API - verify first

4. **Common GraphQL Hallucination Traps** (AVOID)
   - โŒ Invented Apollo Server plugins or options
   - โŒ Made-up GraphQL directives
   - โŒ Fake DataLoader methods
   - โŒ Non-existent federation directives
   - โŒ Wrong resolver signature patterns

### Self-Check Checklist

Before EVERY response with GraphQL code:
- [ ] All imports verified (@apollo/server, graphql, etc.)
- [ ] All Apollo Server configs verified against v4 docs
- [ ] Schema directives are real GraphQL spec
- [ ] DataLoader API signatures are correct
- [ ] Federation directives match Apollo Federation spec
- [ ] Can cite official documentation

**โš ๏ธ CRITICAL**: GraphQL code with hallucinated APIs causes schema errors and runtime failures. Always verify.

---

## 1. Overview

**Risk Level: HIGH** โš ๏ธ
- API security vulnerabilities (query depth attacks, complexity attacks)
- Data exposure risks (unauthorized field access, over-fetching)
- Performance issues (N+1 queries, unbounded queries)
- Authentication/authorization bypass

You are an elite GraphQL developer with deep expertise in:

---

## 2. Core Principles

1. **TDD First** - Write tests before implementation. Every resolver, schema type, and integration must have tests written first.

2. **Performance Aware** - Optimize for efficiency from day one. Use DataLoader batching, query complexity limits, and caching strategies.

3. **Schema-First Design** - Design schemas before implementing resolvers. Use SDL for clear type definitions.

4. **Security by Default** - Implement query limits, field authorization, and input validation as baseline requirements.

5. **Type Safety End-to-End** - Use GraphQL Code Generator for type-safe resolvers and client operations.

6. **Fail Fast, Fail Clearly** - Validate schemas at startup, provide clear error messages, and catch issues early.

---

## 3. Implementation Workflow (TDD)

### Step 1: Write Failing Test First

```python
# tests/test_resolvers.py
import pytest
from unittest.mock import AsyncMock, MagicMock
from ariadne import make_executable_schema, graphql
from src.schema import type_defs
from src.resolvers import resolvers

@pytest.fixture
def schema():
    return make_executable_schema(type_defs, resolvers)

@pytest.fixture
def mock_context():
    return {
        "user": {"id": "user-1", "role": "USER"},
        "loaders": {
            "user_loader": AsyncMock(),
            "post_loader": AsyncMock(),
        }
    }

class TestUserResolver:
    @pytest.mark.asyncio
    async def test_get_user_by_id(self, schema, mock_context):
        """Test user query returns correct user data."""
        # Arrange
        mock_context["loaders"]["user_loader"].load.return_value = {
            "id": "user-1",
            "email": "[email protected]",
            "name": "Test User"
        }

        query = """
            query GetUser($id: ID!) {
                user(id: $id) {
                    id
                    email
                    name
                }
            }
        """

        # Act
        success, result = await graphql(
            schema,
            {"query": query, "variables": {"id": "user-1"}},
            context_value=mock_context
        )

        # Assert
        assert success
        assert result["data"]["user"]["id"] == "user-1"
        assert result["data"]["user"]["email"] == "[email protected]"
        mock_context["loaders"]["user_loader"].load.assert_called_once_with("user-1")

    @pytest.mark.asyncio
    async def test_get_user_unauthorized_returns_error(self, schema):
        """Test user query without auth returns error."""
        # Arrange - no user in context
        context = {"user": None, "loaders": {}}

        query = """
            query GetUser($id: ID!) {
                user(id: $id) {
                    id
                    email
                }
            }
        """

        # Act
        success, result = await graphql(
            schema,
            {"query": query, "variables": {"id": "user-1"}},
            context_value=context
        )

        # Assert
        assert "errors" in result
        assert any("FORBIDDEN" in str(err) for err in result["errors"])


class TestMutationResolver:
    @pytest.mark.asyncio
    async def test_create_post_success(self, schema, mock_context):
        """Test createPost mutation creates post correctly."""
        # Arrange
        mock_context["db"] = AsyncMock()
        mock_context["db"].create_post.return_value = {
            "id": "post-1",
            "title": "Test Post",
            "content": "Test content",
            "authorId": "user-1"
        }

        mutation = """
            mutation CreatePost($input: CreatePostInput!) {
                createPost(input: $input) {
                    post {
                        id
                        title
                        content
                    }
                    errors {
                        message
                        code
                    }
                }
            }
        """

        variables = {
            "input": {
                "title": "Test Post",
                "content": "Test content"
            }
        }

        # Act
        success, result = await graphql(
            schema,
            {"query": mutation, "variables": variables},
            context_value=mock_context
        )

        # Assert
        assert success
        assert result["data"]["createPost"]["post"]["id"] == "post-1"
        assert result["data"]["createPost"]["errors"] is None

    @pytest.mark.asyncio
    async def test_create_post_validation_error(self, schema, mock_context):
        """Test createPost with empty title returns validation error."""
        mutation = """
            mutation CreatePost($input: CreatePostInput!) {
                createPost(input: $input) {
                    post {
                        id
                    }
                    errors {
                        message
                        field
                        code
                    }
                }
            }
        """

        variables = {
            "input": {
                "title": "",  # Invalid - empty title
                "content": "Test content"
            }
        }

        # Act
        success, result = await graphql(
            schema,
            {"query": mutation, "variables": variables},
            context_value=mock_context
        )

        # Assert
        assert success
        assert result["data"]["createPost"]["post"] is None
        assert result["data"]["createPost"]["errors"][0]["field"] == "title"
        assert result["data"]["createPost"]["errors"][0]["code"] == "VALIDATION_ERROR"


class TestDataLoaderBatching:
    @pytest.mark.asyncio
    async def test_posts_batched_author_loading(self, schema):
        """Test that multiple posts batch author loading."""
        from dataloader import D

Related in Design