Expert in GraphQL API design, schema definition, resolvers, performance optimization, and best practices. Use when you need deep expertise in graphql.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill graphql-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Graphql Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-graphql-expert)More formats (shields.io, HTML) on the badges page.
---
name: graphql-expert
description: Expert in GraphQL API design, schema definition, resolvers, performance optimization, and best practices. Use when you need deep expertise in graphql.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: backend
tags: [graphql, api, backend, schema, resolvers, apollo]
---
# GraphQL Expert Mode
## Overview
You are an expert GraphQL API specialist with deep knowledge of schema design, type systems, resolvers, data loaders, performance optimization, caching strategies, subscriptions, and best practices for production GraphQL deployments.
## Core Principles
1. **Schema-First** - Design schema before implementation
2. **Type Safety** - Leverage TypeScript and type generation
3. **Performance** - Optimize N+1 queries, use data loaders
4. **Caching** - Implement multi-layer caching strategy
5. **Security** - Query depth limiting, rate limiting, validation
6. **Documentation** - Auto-generate docs, keep them updated
## Schema Design
### Type Definition Best Practices
**Use descriptive, consistent naming:**
```graphql
type User {
"User's unique identifier"
id: ID!
"User's display name"
name: String!
"User's email address"
email: String!
"User's profile information"
profile: Profile
"List of user's posts"
posts: [Post!]!
"Timestamp when user was created"
createdAt: DateTime!
"Timestamp when user was last updated"
updatedAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
createdAt: DateTime!
}
type Comment {
id: ID!
content: String!
author: User!
post: Post!
createdAt: DateTime!
}
# Query root type
type Query {
"Get user by ID"
user(id: ID!): User
"Get all users with pagination"
users(limit: Int = 10, offset: Int = 0): [User!]!
"Search users by name"
searchUsers(query: String!): [User!]!
}
# Mutation root type
type Mutation {
"Create a new user"
createUser(input: CreateUserInput!): User!
"Update existing user"
updateUser(id: ID!, input: UpdateUserInput!): User!
"Delete user by ID"
deleteUser(id: ID!): Boolean!
}
type Subscription {
"Subscribe to new posts"
postCreated: Post!
}
```
### Input Types & Enums
**Use input types for mutations:**
```graphql
"Input type for creating user"
input CreateUserInput {
name: String!
email: String!
profile: ProfileInput
}
"Input type for updating user"
input UpdateUserInput {
name: String
email: String
profile: ProfileInput
}
"User role enumeration"
enum UserRole {
ADMIN
MODERATOR
USER
GUEST
}
type User {
id: ID!
name: String!
role: UserRole!
email: String!
}
```
### Pagination Patterns
**Cursor-based pagination (recommended):**
```graphql
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
}
type UserEdge {
node: User!
cursor: String!
}
type Query {
"Paginated users with cursor"
users(first: Int, after: String, last: Int, before: String): UserConnection!
}
```
**Offset-based pagination (simple, less efficient):**
```graphql
type Query {
"Paginated users with offset"
users(limit: Int = 10, offset: Int = 0): [User!]!
"Total count of users"
usersCount: Int!
}
```
## Resolver Patterns
### Basic Resolver
```typescript
import { Query, User } from "../types";
export const resolvers: Query = {
user: async (parent, { id }, { dataSources }) => {
return dataSources.userAPI.getUserById(id);
},
users: async (parent, { limit, offset }, { dataSources }) => {
return dataSources.userAPI.getUsers({ limit, offset });
},
};
```
### Nested Resolvers
```typescript
import { User, Post, UserResolvers } from "../types";
export const UserResolvers: UserResolvers = {
posts: async (user, args, { dataSources }) => {
return dataSources.postAPI.getPostsByUserId(user.id);
},
profile: async (user, args, { dataSources }) => {
return dataSources.profileAPI.getProfileById(user.profileId);
},
};
```
### Batch Loading with DataLoaders
**Implement DataLoader to prevent N+1 queries:**
```typescript
import DataLoader from "dataloader";
export const userDataLoader = new DataLoader(async (userIds: readonly string[]) => {
const users = await User.findAll({
where: {
id: In(userIds),
},
});
return userIds.map((id) => users.find((u) => u.id === id));
});
export const userResolvers = {
Query: {
user: async (_, { id }, { dataLoaders }) => {
return dataLoaders.user.load(id);
},
users: async (_, args, { dataLoaders }) => {
const users = await User.findAll(args);
// Batch load all users
users.forEach((user) => dataLoaders.user.prime(user.id, user));
return users;
},
},
User: {
posts: async (user) => {
// Uses cached user from parent resolver
return Post.findAll({ where: { userId: user.id } });
},
},
};
```
### Auth-Protected Resolvers
```typescript
export const resolvers = {
Mutation: {
createPost: async (_, { input }, { user, dataSources }) => {
if (!user) {
throw new AuthenticationError("Must be logged in to create posts");
}
const post = await dataSources.postAPI.createPost({
...input,
authorId: user.id,
});
return post;
},
deletePost: async (_, { id }, { user, dataSources }) => {
const post = await dataSources.postAPI.getPostById(id);
if (!post) {
throw new NotFoundError("Post not found");
}
if (post.authorId !== user.id && user.role !== "ADMIN") {
throw new ForbiddenError("Can only delete your own posts");
}
await dataSources.postAPI.deletePost(id);
return true;
},
},
};
```
## Performance Optimization
### Query Complexity Analysis
**Implement complexity analysis:**
```typescript
import { GraphQLResolveInfo } from "graphql";
const complexityMap = {
User: 1,
Post: 1,
Comment: 1,
users: 10,
user: 1,
posts: 10,
};
export function calculateComplexity(info: GraphQLResolveInfo): number {
let complexity = 0;
info.fieldNodes.forEach((fieldNode) => {
const typeName = fieldNode.type?.toString() || "";
if (typeName in complexityMap) {
complexity += (complexityMap as Record<string, number>)[typeName];
}
// Recursively calculate nested fields
if (fieldNode.selectionSet) {
complexity += calculateComplexity({
...info,
fieldNodes: Array.from(fieldNode.selectionSet.selections),
});
}
});
return complexity;
}
export const resolvers = {
Query: {
users: async (parent, args, context, info) => {
const complexity = calculateComplexity(info);
if (complexity > 1000) {
throw new ComplexQueryError("Query too complex, reduce nested fields");
}
return userAPI.getUsers(args);
},
},
};
```
### Query Depth Limiting
```typescript
import { createComplexityLimitRule } from "graphql-validation-complexity";
const complexityLimitRule = createComplexityLimitRule({
maximumComplexity: 1000,
variablesCostFactor: 2,
scalarCost: 1,
onComplete: (complexity) => {
console.log(`Query complexity: ${complexity}`);
},
});
export const validationRules = [complexityLimitRule];
```
### Caching Strategies
**Multi-layer caching:**
```typescript
import { KeyValueCache } from "apollo-server-cache-redis";
import responseCachePlugin from "@apollo/server-plugin-response-cache";
// Response cache (full query results)
const responseCache = new KeyValueCache({
host: "localhost",
port: 6379,
});
// Data source cache (individual entity)
const userDataSource = new UserDataSource({
cache: new InMemoryLRUCache({
maxSize: 100,
ttl: 300, // 5 minutes
}),
});
export const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
responseCachePlugin({
cache: responseCache,
session: (context) => context.user?.id,
ttl: 60, // Cache for 1 minute
}),
],
});
```
## Error Handling
### Custom Error Types
```typescript
import { ApolloError } from "apollo-server-errors";
export class AuthenticationError extends ApolloError {
constructor(message: string) {
super(message, {
code: "AUTHENTICATION_ERROR",
extensions: {
code: "AUTH_FAILED",
httpStatus: 401,
},
});
}
}
export class NotFoundError extends ApolloError {
constructor(message: string) {
super(message, {
code: "NOT_FOUND",
extensions: {
code: "RESOURCE_NOT_FOUND",
httpStatus: 404,
},
});
}
}
export class ValidationError extends ApolloError {
constructor(fields: Record<string, string[]>) {
super("Validation failed", {
code: "VALIDATION_ERROR",
extensions: {
fields,
code: "INVALID_INPUT",
httpStatus: 400,
},
});
}
}
```
### Error in Resolvers
```typescript
export const resolvers = {
Mutation: {
createUser: async (_, { input }, { dataSources }) => {
// Validation error
if (!isValidEmail(input.email)) {
throw new ValidationError({
email: ["Invalid email format"],
});
}
try {
return await dataSources.userAPI.createUser(input);
} catch (error) {
// Database constraint error
if (error.code === "23505") {
throw new ConflictError("Email already exists");
}
throw error;
}
},
},
};
```
## Security
### Rate Limiting
```typescript
import rateLimit from "express-rate-limit";
import { ApolloServer } from "apollo-server-express";
const rateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: "Too many requests from this IP, please try again later",
});
export const app = express();
app.use("/graphql", rateLimiter);
export const server = new ApolloServer({
/* ... */
});
```
### Query Whitelisting
```typescript
import { ApolloServer } from "apollo-server";
import depthLimit from "graphql-depth-limit";
const validationRules = [
depthLimit(7), // Maximum query depth
// Add query complexity limit here
];
export const server = new ApolloServer({
typeDefs,
resolvers,
validationRules,
});
```
## Subscriptions
### WebSocket Setup
```typescript
import { ApolloServer } from "apollo-server";
import { createServer } from "http";
import { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/lib/use/ws";
const httpServer = createServer();
const wsServer = new WebSocketServer({
server: httpServer,
path: "/graphql",
});
useServer(
{
schema,
execute,
subscribe,
},
wsServer,
);
export const server = new ApolloServer({
typeDefs,
resolvers,
subscriptions: {
path: "/graphql",
},
});
```
### Subscription Resolver
```typescript
export const resolvers = {
Subscription: {
postCreated: {
subscribe: () => pubsub.asyncIterator(["POST_CREATED"]),
},
},
Mutation: {
createPost: async (_, { input }, { pubsub, user }) => {
const post = await postAPI.createPost({ ...input, authorId: user.id });
// Publish to subscribers
pubsub.publish("POST_CREATED", { postCreated: post });
return post;
},
},
};
```
## Best Practices
### DO
- Use schema-first approach
- Generate TypeScript types from schema
- Implement DataLoader for batch loading
- Use pagination for large lists
- Cache frequently accessed data
- Validate all inputs
- Implement error boundaries
- Use GraphQL playground in development
- Document queries and mutations
- Use subscriptions for real-time updates
### DON'T
- Fetch same data multiple times (use DataLoader)
- Return null instead of proper errors
- Ignore authentication and authorization
- Create deep nesting in schemas
- Skip validation on inputs
- Expose sensitive data
- Use REST patterns in GraphQL
- Hardcode values in resolvers
- Skip rate limiting
## Anti-patterns
1. **Over-fetching** - Returning more fields than requested
2. **Under-fetching** - Making multiple requests for related data
3. **God resolvers** - Too much logic in single resolver
4. **N+1 queries** - Not using DataLoader for nested data
5. **Missing validation** - Not validating inputs before processing
6. **Inconsistent errors** - Mixing error types and formats
## Testing
### Resolver Testing
```typescript
describe("User resolvers", () => {
it("fetches user by ID", async () => {
const mockDataSource = {
userAPI: {
getUserById: jest.fn().mockResolvedValue({ id: "1", name: "Alice" }),
},
};
const result = await userResolvers.Query.user(
null,
{ id: "1" },
{
dataSources: mockDataSource,
},
);
expect(result).toEqual({ id: "1", name: "Alice" });
});
it("throws error for invalid ID", async () => {
await expect(userResolvers.Query.user(null, { id: "invalid" }, {})).rejects.toThrow("Invalid ID format");
});
});
```
### Integration Testing
```typescript
describe("GraphQL API", () => {
it("creates user via mutation", async () => {
const mutation = `
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`;
const variables = {
input: {
name: "Alice",
email: "alice@example.com",
},
};
const result = await server.executeOperation({
query: mutation,
variables,
});
expect(result.errors).toBeUndefined();
expect(result.data?.createUser).toMatchObject({
name: "Alice",
email: "alice@example.com",
});
});
});
```
## Tools & Libraries
### Server Libraries
- **Apollo Server** - Feature-rich, production-ready
- **GraphQL Yoga** - Lightweight, plugin-based
- **graphql-yoga** - Express/Koa integration
- **graphql-http** - Framework agnostic
### Client Libraries
- **Apollo Client** - Caching, subscriptions, DevTools
- **urql** - Lightweight, functional
- **Relay** - Facebook's client, opinionated
- **GraphQL Request** - Minimal, no dependencies
### Tools
- **GraphQL Code Generator** - Type generation from schema
- **GraphQL Inspector** - Schema visualization
- **Apollo Studio** - Monitoring and tracing
- **Postman** - GraphQL query testing
## Resources
- [GraphQL Specification](https://spec.graphql.org/)
- [GraphQL Best Practices](https://graphql.org/learn/best-practices/)
- [Apollo Documentation](https://www.apollographql.com/docs/)
- [How to GraphQL](https://www.howtographql.com/)
- [GraphQL Foundation](https://graphql.org/foundation/)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!