🎯

graphql-api-development

🎯Skill

from manutej/crush-mcp-server

VibeIndex|
What it does

Develops comprehensive, flexible GraphQL APIs with precise data fetching, strong typing, and advanced querying capabilities for modern web applications.

πŸ“¦

Part of

manutej/crush-mcp-server(20 items)

graphql-api-development

Installation

npm runRun npm script
npm run build
npm runRun npm script
npm run build # Compile TypeScript
npm runRun npm script
npm run dev # Watch mode (auto-rebuild)
πŸ“– Extracted from docs: manutej/crush-mcp-server
5Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

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

Overview

# 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 GraphQLList(PostType),

resolve: (user, args, context) => {

return context.loaders.postLoader.load(user.id);

}

}

}

});

// Add to context

const context = {

loaders: {

postLoader: new DataLoader(batchLoadPosts)

}

};

```

Query Complexity Analysis

Limit expensive queries:

```javascript

import { getComplexity, simpleEstimator } from 'graphql-query-complexity';

const complexity = getComplexity({

schema,

query,

estimators: [

simpleEstimator({ defaultComplexity: 1 })

]

});

if (complexity > 1000) {

throw new Error('Query too complex');

}

```

Depth Limiting

Prevent deeply nested queries:

```javascript

import depthLimit from 'graphql-depth-limit';

const server = new ApolloServer({

schema,

validationRules: [depthLimit(5)]

});

```

Mutations and Input Validation

Mutation Design Pattern

```graphql

input CreatePostInput {

title: String!

content: String!

authorId: ID!

tags: [String!]

}

type CreatePostPayload {

post: Post

errors: [UserError!]

success: Boolean!

}

type UserError {

message: String!

field: String

}

type Mutation {

createPost(input: CreatePostInput!): CreatePostPayload!

}

```

Input Validation

```javascript

const Mutation = new GraphQLObjectType({

name: 'Mutation',

fields: {

createPost: {

type: CreatePostPayload,

args: {

input: { type: new GraphQLNonNull(CreatePostInput) }

},

resolve: async (_, { input }, context) => {

// Validate input

const errors = [];

if (input.title.length < 3) {

errors.push({

field: 'title',

message: 'Title must be at least 3 characters'

});

}

if (input.content.length < 10) {

errors.push({

field: 'content',

message: 'Content must be at least 10 characters'

});

}

if (errors.length > 0) {

return { errors, success: false, post: null };

}

// Create post

const post = await context.db.createPost(input);

return { post, errors: [], success: true };

}

}

}

});

```

Subscriptions and Real-time Updates

Setting Up Subscriptions

```javascript

import { GraphQLObjectType, GraphQLString } from 'graphql';

import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();

const Subscription = new GraphQLObjectType({

name: 'Subscription',

fields: {

postCreated: {

type: PostType,

subscribe: () => pubsub.asyncIterator(['POST_CREATED'])

},

messageReceived: {

type: MessageType,

args: {

channelId: { type: new GraphQLNonNull(GraphQLID) }

},

subscribe: (_, { channelId }) => {

return pubsub.asyncIterator([MESSAGE_${channelId}]);

}

}

}

});

```

Publishing Events

```javascript

const Mutation = new GraphQLObjectType({

name: 'Mutation',

fields: {

createPost: {

type: PostType,

args: {

input: { type: new GraphQLNonNull(CreatePostInput) }

},

resolve: async (_, { input }, context) => {

const post = await context.db.createPost(input);

// Publish to subscribers

pubsub.publish('POST_CREATED', { postCreated: post });

return post;

}

}

}

});

```

WebSocket Server Setup

```javascript

import { createServer } from 'http';

import { WebSocketServer } from 'ws';

import { useServer } from 'graphql-ws/lib/use/ws';

import { execute, subscribe } from 'graphql';

import express from 'express';

const app = express();

const httpServer = createServer(app);

// WebSocket server for subscriptions

const wsServer = new WebSocketServer({

server: httpServer,

path: '/graphql'

});

useServer(

{

schema,

execute,

subscribe,

context: (ctx) => {

// Access connection params, headers

return {

userId: ctx.connectionParams?.userId,

db: database

};

}

},

wsServer

);

httpServer.listen(4000);

```

Authentication and Authorization

Context-Based Authentication

```javascript

import jwt from 'jsonwebtoken';

// Middleware to extract user

const authMiddleware = async (req) => {

const token = req.headers.authorization?.replace('Bearer ', '');

if (!token) {

return { user: null };

}

try {

const decoded = jwt.verify(token, process.env.JWT_SECRET);

const user = await db.findUserById(decoded.userId);

return { user };

} catch (error) {

return { user: null };

}

};

// Add to GraphQL context

app.all('/graphql', async (req, res) => {

const auth = await authMiddleware(req);

createHandler({

schema,

context: {

user: auth.user,

db: database

}

})(req, res);

});

```

Resolver-Level Authorization

```javascript

const Query = new GraphQLObjectType({

name: 'Query',

fields: {

me: {

type: UserType,

resolve: (_, __, context) => {

if (!context.user) {

throw new Error('Authentication required');

}

return context.user;

}

},

adminData: {

type: GraphQLString,

resolve: (_, __, context) => {

if (!context.user) {

throw new Error('Authentication required');

}

if (context.user.role !== 'admin') {

throw new Error('Admin access required');

}

return 'Secret admin data';

}

}

}

});

```

Field-Level Authorization

```javascript

const PostType = new GraphQLObjectType({

name: 'Post',

fields: {

title: { type: GraphQLString },

content: { type: GraphQLString },

draft: {

type: GraphQLBoolean,

resolve: (post, args, context) => {

// Only author can see draft status

if (post.authorId !== context.user?.id) {

return null;

}

return post.draft;

}

}

}

});

```

Directive-Based Authorization

```graphql

directive @auth(requires: Role = USER) on FIELD_DEFINITION

enum Role {

USER

ADMIN

MODERATOR

}

type Query {

publicData: String

userData: String @auth(requires: USER)

adminData: String @auth(requires: ADMIN)

}

```

```javascript

import { mapSchema, getDirective, MapperKind } from '@graphql-tools/utils';

function authDirective(schema, directiveName) {

return mapSchema(schema, {

[MapperKind.OBJECT_FIELD]: (fieldConfig) => {

const authDirective = getDirective(schema, fieldConfig, directiveName)?.[0];

if (authDirective) {

const { requires } = authDirective;

const { resolve = defaultFieldResolver } = fieldConfig;

fieldConfig.resolve = async (source, args, context, info) => {

if (!context.user) {

throw new Error('Authentication required');

}

if (context.user.role !== requires) {

throw new Error(${requires} role required);

}

return resolve(source, args, context, info);

};

}

return fieldConfig;

}

});

}

```

Caching Strategies

In-Memory Caching

```javascript

import { LRUCache } from 'lru-cache';

const cache = new LRUCache({

max: 500,

ttl: 1000 60 5 // 5 minutes

});

const Query = new GraphQLObjectType({

name: 'Query',

fields: {

product: {

type: ProductType,

args: { id: { type: new GraphQLNonNull(GraphQLID) } },

resolve: async (_, { id }, context) => {

const cacheKey = product:${id};

const cached = cache.get(cacheKey);

if (cached) {

return cached;

}

const product = await context.db.findProductById(id);

cache.set(cacheKey, product);

return product;

}

}

}

});

```

Redis Caching

```javascript

import Redis from 'ioredis';

const redis = new Redis({

host: process.env.REDIS_HOST,

port: process.env.REDIS_PORT

});

const Query = new GraphQLObjectType({

name: 'Query',

fields: {

user: {

type: UserType,

args: { id: { type: new GraphQLNonNull(GraphQLID) } },

resolve: async (_, { id }, context) => {

const cacheKey = user:${id};

// Check cache

const cached = await redis.get(cacheKey);

if (cached) {

return JSON.parse(cached);

}

// Fetch from database

const user = await context.db.findUserById(id);

// Cache for 10 minutes

await redis.setex(cacheKey, 600, JSON.stringify(user));

return user;

}

}

}

});

```

Cache Invalidation

```javascript

const Mutation = new GraphQLObjectType({

name: 'Mutation',

fields: {

updateUser: {

type: UserType,

args: {

id: { type: new GraphQLNonNull(GraphQLID) },

input: { type: new GraphQLNonNull(UpdateUserInput) }

},

resolve: async (_, { id, input }, context) => {

const user = await context.db.updateUser(id, input);

// Invalidate cache

const cacheKey = user:${id};

await redis.del(cacheKey);

// Also invalidate list caches

await redis.del('users:all');

return user;

}

}

}

});

```

Error Handling

Custom Error Classes

```javascript

class AuthenticationError extends Error {

constructor(message) {

super(message);

this.name = 'AuthenticationError';

this.extensions = { code: 'UNAUTHENTICATED' };

}

}

class ForbiddenError extends Error {

constructor(message) {

super(message);

this.name = 'ForbiddenError';

this.extensions = { code: 'FORBIDDEN' };

}

}

class ValidationError extends Error {

constructor(message, fields) {

super(message);

this.name = 'ValidationError';

this.extensions = {

code: 'BAD_USER_INPUT',

fields

};

}

}

```

Error Formatting

```javascript

import { formatError } from 'graphql';

const customFormatError = (error) => {

// Log error for monitoring

console.error('GraphQL Error:', {

message: error.message,

locations: error.locations,

path: error.path,

extensions: error.extensions

});

// Don't expose internal errors to clients

if (error.message.startsWith('Database')) {

return {

message: 'Internal server error',

extensions: { code: 'INTERNAL_SERVER_ERROR' }

};

}

return formatError(error);

};

const server = new ApolloServer({

schema,

formatError: customFormatError

});

```

Graceful Error Responses

```javascript

const Query = new GraphQLObjectType({

name: 'Query',

fields: {

user: {

type: UserType,

args: { id: { type: new GraphQLNonNull(GraphQLID) } },

resolve: async (_, { id }, context) => {

try {

const user = await context.db.findUserById(id);

if (!user) {

throw new Error(User with ID ${id} not found);

}

return user;

} catch (error) {

// Log error

console.error('Error fetching user:', error);

// Re-throw with user-friendly message

if (error.code === 'ECONNREFUSED') {

throw new Error('Unable to connect to database');

}

throw error;

}

}

}

}

});

```

Testing GraphQL APIs

Unit Testing Resolvers

```javascript

import { describe, it, expect, jest } from '@jest/globals';

describe('User resolver', () => {

it('returns user by ID', async () => {

const mockDb = {

findUserById: jest.fn().mockResolvedValue({

id: '1',

name: 'Alice',

email: 'alice@example.com'

})

};

const context = { db: mockDb };

const result = await userResolver.resolve(null, { id: '1' }, context);

expect(mockDb.findUserById).toHaveBeenCalledWith('1');

expect(result).toEqual({

id: '1',

name: 'Alice',

email: 'alice@example.com'

});

});

it('throws error for non-existent user', async () => {

const mockDb = {

findUserById: jest.fn().mockResolvedValue(null)

};

const context = { db: mockDb };

await expect(

userResolver.resolve(null, { id: '999' }, context)

).rejects.toThrow('User with ID 999 not found');

});

});

```

Integration Testing

```javascript

import { graphql } from 'graphql';

import { schema } from './schema';

describe('GraphQL Schema', () => {

it('executes user query', async () => {

const query = `

query {

user(id: "1") {

id

name

email

}

}

`;

const result = await graphql({

schema,

source: query,

contextValue: {

db: mockDatabase,

user: null

}

});

expect(result.errors).toBeUndefined();

expect(result.data?.user).toEqual({

id: '1',

name: 'Alice',

email: 'alice@example.com'

});

});

it('handles authentication errors', async () => {

const query = `

query {

me {

id

name

}

}

`;

const result = await graphql({

schema,

source: query,

contextValue: {

db: mockDatabase,

user: null

}

});

expect(result.errors).toBeDefined();

expect(result.errors[0].message).toBe('Authentication required');

});

});

```

Testing with Apollo Server

```javascript

import { ApolloServer } from '@apollo/server';

const testServer = new ApolloServer({

schema,

});

describe('User queries', () => {

it('fetches user successfully', async () => {

const response = await testServer.executeOperation({

query: `

query GetUser($id: ID!) {

user(id: $id) {

id

name

}

}

`,

variables: { id: '1' }

});

expect(response.body.singleResult.errors).toBeUndefined();

expect(response.body.singleResult.data?.user).toMatchObject({

id: '1',

name: expect.any(String)

});

});

});

```

Production Best Practices

Schema Organization

```

src/

β”œβ”€β”€ schema/

β”‚ β”œβ”€β”€ index.js # Combine all types

β”‚ β”œβ”€β”€ types/

β”‚ β”‚ β”œβ”€β”€ user.js # User type and resolvers

β”‚ β”‚ β”œβ”€β”€ post.js # Post type and resolvers

β”‚ β”‚ └── comment.js # Comment type and resolvers

β”‚ β”œβ”€β”€ queries/

β”‚ β”‚ β”œβ”€β”€ user.js # User queries

β”‚ β”‚ └── post.js # Post queries

β”‚ β”œβ”€β”€ mutations/

β”‚ β”‚ β”œβ”€β”€ user.js # User mutations

β”‚ β”‚ └── post.js # Post mutations

β”‚ └── subscriptions/

β”‚ └── post.js # Post subscriptions

β”œβ”€β”€ directives/

β”‚ └── auth.js # Authorization directive

β”œβ”€β”€ utils/

β”‚ β”œβ”€β”€ loaders.js # DataLoader instances

β”‚ └── context.js # Context builder

└── server.js # Server setup

```

Monitoring and Logging

```javascript

import { ApolloServerPluginLandingPageGraphQLPlayground } from '@apollo/server-plugin-landing-page-graphql-playground';

const server = new ApolloServer({

schema,

plugins: [

// Request logging

{

async requestDidStart(requestContext) {

console.log('Request started:', requestContext.request.query);

return {

async didEncounterErrors(ctx) {

console.error('Errors:', ctx.errors);

},

async willSendResponse(ctx) {

console.log('Response sent');

}

};

}

},

// Performance monitoring

{

async requestDidStart() {

const start = Date.now();

return {

async willSendResponse() {

const duration = Date.now() - start;

console.log(Request duration: ${duration}ms);

}

};

}

}

]

});

```

Rate Limiting

```javascript

import rateLimit from 'express-rate-limit';

const limiter = rateLimit({

windowMs: 15 60 1000, // 15 minutes

max: 100, // Limit each IP to 100 requests per window

message: 'Too many requests, please try again later'

});

app.use('/graphql', limiter);

```

Query Whitelisting

```javascript

const allowedQueries = new Set([

'query GetUser { user(id: $id) { id name email } }',

'mutation CreatePost { createPost(input: $input) { id title } }'

]);

const validateQuery = (query) => {

const normalized = query.replace(/\s+/g, ' ').trim();

if (!allowedQueries.has(normalized)) {

throw new Error('Query not whitelisted');

}

};

```

Security Headers

```javascript

import helmet from 'helmet';

app.use(helmet({

contentSecurityPolicy: {

directives: {

defaultSrc: ["'self'"],

scriptSrc: ["'self'", "'unsafe-inline'"],

}

},

crossOriginEmbedderPolicy: false

}));

```

Advanced Patterns

Federation (Microservices)

```javascript

import { buildSubgraphSchema } from '@apollo/subgraph';

// Users service

const userSchema = buildSubgraphSchema({

typeDefs: `

type User @key(fields: "id") {

id: ID!

name: String!

email: String!

}

`,

resolvers: {

User: {

__resolveReference(user) {

return findUserById(user.id);

}

}

}

});

// Posts service

const postSchema = buildSubgraphSchema({

typeDefs: `

type Post {

id: ID!

title: String!

author: User!

}

extend type User @key(fields: "id") {

id: ID! @external

posts: [Post!]!

}

`,

resolvers: {

Post: {

author(post) {

return { __typename: 'User', id: post.authorId };

}

},

User: {

posts(user) {

return findPostsByAuthorId(user.id);

}

}

}

});

```

Custom Scalars

```javascript

import { GraphQLScalarType, Kind } from 'graphql';

const DateTimeScalar = new GraphQLScalarType({

name: 'DateTime',

description: 'ISO-8601 DateTime string',

serialize(value) {

// Send to client

return value instanceof Date ? value.toISOString() : null;

},

parseValue(value) {

// From variables

return new Date(value);

},

parseLiteral(ast) {

// From query string

if (ast.kind === Kind.STRING) {

return new Date(ast.value);

}

return null;

}

});

// Use in schema

const schema = new GraphQLSchema({

types: [DateTimeScalar],

query: new GraphQLObjectType({

name: 'Query',

fields: {

now: {

type: DateTimeScalar,

resolve: () => new Date()

}

}

})

});

```

Batch Operations

```javascript

const Mutation = new GraphQLObjectType({

name: 'Mutation',

fields: {

batchCreateUsers: {

type: new GraphQLList(UserType),

args: {

inputs: {

type: new GraphQLNonNull(

new GraphQLList(new GraphQLNonNull(CreateUserInput))

)

}

},

resolve: async (_, { inputs }, context) => {

const users = await Promise.all(

inputs.map(input => context.db.createUser(input))

);

return users;

}

}

}

});

```

Common Patterns Summary

  1. Use Input Types: For all mutations with multiple arguments
  2. Implement DataLoader: Solve N+1 queries for nested data
  3. Add Pagination: For list fields that can grow unbounded
  4. Handle Errors Gracefully: Return user-friendly error messages
  5. Validate Inputs: At resolver level before database operations
  6. Use Context for Shared State: Database, authentication, loaders
  7. Implement Authorization: At resolver or directive level
  8. Cache Aggressively: Use Redis or in-memory for frequently accessed data
  9. Monitor Performance: Track query complexity and execution time
  10. Version with @deprecated: Never break existing queries
  11. Test Thoroughly: Unit test resolvers, integration test queries
  12. Document Schema: Use descriptions in SDL
  13. Use Non-Null Wisely: Only for truly required fields
  14. Organize Schema: Split into modules by domain
  15. Secure Production: Rate limiting, query whitelisting, depth limiting

Resources and Tools

Essential Libraries

  • graphql-js: Core GraphQL implementation
  • express: Web server framework
  • graphql-http: HTTP handler for GraphQL
  • dataloader: Batching and caching
  • graphql-ws: WebSocket server for subscriptions
  • graphql-scalars: Common custom scalars
  • graphql-tools: Schema manipulation utilities

Development Tools

  • GraphiQL: In-browser GraphQL IDE
  • GraphQL Playground: Advanced GraphQL IDE
  • Apollo Studio: Schema registry and monitoring
  • GraphQL Code Generator: Generate TypeScript types
  • eslint-plugin-graphql: Lint GraphQL queries

Learning Resources

  • GraphQL Official Documentation: https://graphql.org
  • GraphQL.js Repository: https://github.com/graphql/graphql-js
  • How to GraphQL: https://howtographql.com
  • Apollo GraphQL: https://apollographql.com
  • GraphQL Weekly Newsletter: https://graphqlweekly.com

---

Skill Version: 1.0.0

Last Updated: October 2025

Skill Category: API Development, Backend, GraphQL, Web Development

Compatible With: Node.js, Express, TypeScript, JavaScript

More from this repository10

🎯
n8n-mcp-orchestrator🎯Skill

Automates workflow integration and task orchestration between n8n and MCP systems, enabling seamless data synchronization and process automation

🎯
category-master🎯Skill

category-master skill from manutej/crush-mcp-server

🎯
fp-ts🎯Skill

fp-ts skill from manutej/crush-mcp-server

🎯
mcp-integration-expert🎯Skill

mcp-integration-expert skill from manutej/crush-mcp-server

🎯
supabase-mcp-integration🎯Skill

supabase-mcp-integration skill from manutej/crush-mcp-server

🎯
jest-react-testing🎯Skill

Enables comprehensive React component testing using Jest and React Testing Library, covering configuration, mocking, async testing, and best practices.

🎯
api-gateway-patterns🎯Skill

Implements production-grade API gateway patterns using Kong, covering routing, authentication, rate limiting, load balancing, and microservices traffic management.

🎯
pytest-patterns🎯Skill

Streamlines Python testing with pytest by providing comprehensive patterns for fixtures, parametrization, mocking, and test organization.

🎯
docker-compose-orchestration🎯Skill

Orchestrates multi-container Docker applications by defining, configuring, and managing complex service architectures with networking, volumes, and deployment strategies.

🎯
pytest🎯Skill

Enables comprehensive Python unit testing for customer support systems, covering FastAPI, SQLAlchemy, async operations, and complex backend testing scenarios.