graphql

GraphQL API design, implementation, and best practices.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "graphql" with this command: npx skills add vapvarun/claude-backup/vapvarun-claude-backup-graphql

GraphQL Development

GraphQL API design, implementation, and best practices.

Schema Design

Type Definitions

Scalar types

type User { id: ID! email: String! name: String age: Int balance: Float isActive: Boolean! createdAt: DateTime! # Custom scalar }

Enum types

enum UserRole { ADMIN EDITOR USER }

enum OrderStatus { PENDING PROCESSING SHIPPED DELIVERED CANCELLED }

Input types (for mutations)

input CreateUserInput { email: String! name: String! password: String! role: UserRole = USER }

input UpdateUserInput { name: String email: String }

Interface

interface Node { id: ID! }

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

Union types

union SearchResult = User | Post | Comment

Relationships

type User { id: ID! email: String! posts: [Post!]! # One-to-many profile: Profile # One-to-one (nullable) followers: [User!]! # Self-referential following: [User!]! }

type Post { id: ID! title: String! content: String! author: User! # Many-to-one tags: [Tag!]! # Many-to-many comments(first: Int, after: String): CommentConnection! }

Connection pattern for pagination

type CommentConnection { edges: [CommentEdge!]! pageInfo: PageInfo! totalCount: Int! }

type CommentEdge { cursor: String! node: Comment! }

type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String }

Queries & Mutations

Query Types

type Query { # Single item user(id: ID!): User userByEmail(email: String!): User

# Lists with filtering
users(
    filter: UserFilter
    orderBy: UserOrderBy
    first: Int
    after: String
): UserConnection!

# Search
search(query: String!, types: [SearchType!]): [SearchResult!]!

# Current user
me: User

}

input UserFilter { role: UserRole isActive: Boolean createdAfter: DateTime }

input UserOrderBy { field: UserSortField! direction: SortDirection! }

enum UserSortField { CREATED_AT NAME EMAIL }

enum SortDirection { ASC DESC }

Mutation Types

type Mutation { # Create createUser(input: CreateUserInput!): CreateUserPayload!

# Update
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!

# Delete
deleteUser(id: ID!): DeleteUserPayload!

# Authentication
login(email: String!, password: String!): AuthPayload!
logout: Boolean!
refreshToken(token: String!): AuthPayload!

}

Payload pattern (recommended)

type CreateUserPayload { user: User errors: [Error!] }

type Error { field: String message: String! code: ErrorCode! }

enum ErrorCode { VALIDATION_ERROR NOT_FOUND UNAUTHORIZED FORBIDDEN }

Subscriptions

type Subscription { # Real-time updates postCreated: Post! commentAdded(postId: ID!): Comment! userStatusChanged(userId: ID!): User!

# With filtering
messageReceived(roomId: ID!): Message!

}

Apollo Server (Node.js)

Setup

import { ApolloServer } from '@apollo/server'; import { expressMiddleware } from '@apollo/server/express4'; import express from 'express';

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

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

`;

const resolvers = { Query: { users: async (, __, { dataSources }) => { return dataSources.userAPI.getUsers(); }, user: async (, { id }, { dataSources }) => { return dataSources.userAPI.getUser(id); }, }, User: { posts: async (parent, _, { dataSources }) => { return dataSources.postAPI.getPostsByAuthor(parent.id); }, }, };

const server = new ApolloServer({ typeDefs, resolvers, });

const app = express(); await server.start();

app.use( '/graphql', express.json(), expressMiddleware(server, { context: async ({ req }) => ({ token: req.headers.authorization, dataSources: { userAPI: new UserAPI(), postAPI: new PostAPI(), }, }), }) );

Resolvers

const resolvers = { Query: { // Arguments: parent, args, context, info user: async (_, { id }, { dataSources, user }) => { return dataSources.userAPI.getUser(id); },

    users: async (_, { filter, first, after }, { dataSources }) => {
        const users = await dataSources.userAPI.getUsers({
            filter,
            limit: first,
            cursor: after,
        });
        return formatConnection(users);
    },
},

Mutation: {
    createUser: async (_, { input }, { dataSources }) => {
        try {
            const user = await dataSources.userAPI.create(input);
            return { user, errors: null };
        } catch (error) {
            return {
                user: null,
                errors: [{ message: error.message, code: 'VALIDATION_ERROR' }],
            };
        }
    },
},

// Field-level resolvers
User: {
    fullName: (parent) => `${parent.firstName} ${parent.lastName}`,
    posts: async (parent, { first }, { dataSources }) => {
        return dataSources.postAPI.getByAuthor(parent.id, { limit: first });
    },
},

// Custom scalars
DateTime: new GraphQLScalarType({
    name: 'DateTime',
    parseValue: (value) => new Date(value),
    serialize: (value) => value.toISOString(),
}),

};

DataLoader (N+1 Prevention)

import DataLoader from 'dataloader';

// Create loader const userLoader = new DataLoader(async (userIds) => { const users = await db.users.findMany({ where: { id: { in: userIds } }, }); // Return in same order as input return userIds.map((id) => users.find((u) => u.id === id)); });

// In resolver const resolvers = { Post: { author: (parent, _, { loaders }) => { return loaders.userLoader.load(parent.authorId); }, }, };

// Context setup const context = ({ req }) => ({ loaders: { userLoader: new DataLoader(batchUsers), }, });

Authentication & Authorization

Context-based Auth

const server = new ApolloServer({ typeDefs, resolvers, context: async ({ req }) => { const token = req.headers.authorization?.replace('Bearer ', ''); let user = null;

    if (token) {
        try {
            user = await verifyToken(token);
        } catch (e) {
            // Invalid token, user stays null
        }
    }

    return { user };
},

});

// In resolver const resolvers = { Query: { me: (_, __, { user }) => { if (!user) throw new AuthenticationError('Not authenticated'); return user; }, }, };

Directive-based Auth

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

type Query { publicPosts: [Post!]! myPosts: [Post!]! @auth allUsers: [User!]! @auth(requires: ADMIN) }

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

function authDirective(directiveName) { return { authDirectiveTransformer: (schema) => mapSchema(schema, { [MapperKind.OBJECT_FIELD]: (fieldConfig) => { const directive = getDirective(schema, fieldConfig, directiveName)?.[0]; if (directive) { const { resolve = defaultFieldResolver } = fieldConfig; fieldConfig.resolve = async function (source, args, context, info) { if (!context.user) { throw new AuthenticationError('Not authenticated'); } const requiredRole = directive.requires; if (requiredRole && context.user.role !== requiredRole) { throw new ForbiddenError('Not authorized'); } return resolve(source, args, context, info); }; } return fieldConfig; }, }), }; }

Error Handling

import { GraphQLError } from 'graphql';

// Custom errors class NotFoundError extends GraphQLError { constructor(message) { super(message, { extensions: { code: 'NOT_FOUND', http: { status: 404 }, }, }); } }

class ValidationError extends GraphQLError { constructor(errors) { super('Validation failed', { extensions: { code: 'VALIDATION_ERROR', validationErrors: errors, http: { status: 400 }, }, }); } }

// Usage in resolver const resolvers = { Query: { user: async (_, { id }) => { const user = await db.users.findUnique({ where: { id } }); if (!user) { throw new NotFoundError(User ${id} not found); } return user; }, }, };

Performance

Query Complexity

import { createComplexityLimitRule } from 'graphql-validation-complexity';

const server = new ApolloServer({ typeDefs, resolvers, validationRules: [ createComplexityLimitRule(1000, { scalarCost: 1, objectCost: 10, listFactor: 20, }), ], });

Depth Limiting

import depthLimit from 'graphql-depth-limit';

const server = new ApolloServer({ typeDefs, resolvers, validationRules: [depthLimit(10)], });

Caching

type Query { user(id: ID!): User @cacheControl(maxAge: 60) posts: [Post!]! @cacheControl(maxAge: 30, scope: PUBLIC) }

type User @cacheControl(maxAge: 120) { id: ID! email: String! @cacheControl(maxAge: 0, scope: PRIVATE) }

Testing

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

describe('User Queries', () => { let server;

beforeAll(() => {
    server = new ApolloServer({
        typeDefs,
        resolvers,
    });
});

it('should return user by id', async () => {
    const response = await server.executeOperation({
        query: `
            query GetUser($id: ID!) {
                user(id: $id) {
                    id
                    email
                }
            }
        `,
        variables: { id: '1' },
    });

    expect(response.body.singleResult.errors).toBeUndefined();
    expect(response.body.singleResult.data?.user).toEqual({
        id: '1',
        email: 'test@example.com',
    });
});

});

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

php

No summary provided by upstream source.

Repository SourceNeeds Review
General

laravel

No summary provided by upstream source.

Repository SourceNeeds Review
General

javascript

No summary provided by upstream source.

Repository SourceNeeds Review
General

email-marketing

No summary provided by upstream source.

Repository SourceNeeds Review