| name | graphql |
| description | GraphQL schema design, resolvers, mutations, subscriptions, DataLoader, Prisma integration, N+1 prevention |
GraphQL Skill
When to activate
- Designing a GraphQL schema (types, queries, mutations, subscriptions)
- Implementing resolvers in Node.js (Apollo Server, Pothos, GraphQL Yoga)
- Setting up Prisma with a GraphQL API
- Writing GraphQL queries and fragments for a frontend client
- Implementing cursor-based or offset pagination in GraphQL
- Setting up DataLoader for N+1 query prevention
- Adding authentication and authorization to a GraphQL API
- Debugging GraphQL performance or N+1 issues
When NOT to use
- REST APIs where GraphQL adds complexity without benefit (simple CRUD, webhooks, file uploads)
- gRPC or event-driven systems
- Cases where the client always needs the full response (GraphQL's field selection adds no value)
Instructions
Schema design principles
type Order {
id: ID!
status: OrderStatus!
customer: Customer!
items: [OrderItem!]!
totalAmount: Float!
createdAt: DateTime!
}
enum OrderStatus {
PENDING
COMPLETED
CANCELLED
}
type Query {
order(id: ID!): Order
orders(filter: OrderFilter): [Order!]!
me: User
}
type Mutation {
createOrder(input: CreateOrderInput!): CreateOrderPayload!
}
type CreateOrderPayload {
order: Order
errors: [UserError!]!
}
type UserError {
field: String
message: String!
}
N+1 prevention with DataLoader
import DataLoader from 'dataloader';
export function createLoaders() {
return {
customerLoader: new DataLoader<string, Customer>(async (ids) => {
const customers = await db.customer.findMany({
where: { id: { in: [...ids] } }
});
const customerMap = new Map(customers.map(c => [c.id, c]));
return ids.map(id => customerMap.get(id) ?? new Error(`Customer ${id} not found`));
}),
};
}
const resolvers = {
Order: {
customer: (order, _, { loaders }) => loaders.customerLoader.load(order.customerId),
}
};
Cursor-based pagination (preferred over offset)
type OrderConnection {
edges: [OrderEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type OrderEdge {
node: Order!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
orders(first: Int, after: String, last: Int, before: String): OrderConnection!
}
Authorization pattern
const resolvers = {
Query: {
adminStats: (_, __, { user }) => {
if (!user || user.role !== 'ADMIN') {
throw new GraphQLError('Unauthorized', {
extensions: { code: 'UNAUTHORIZED' }
});
}
return getAdminStats();
}
},
Order: {
internalNotes: (order, _, { user }) => {
if (user?.id !== order.customerId && user?.role !== 'ADMIN') return null;
return order.internalNotes;
}
}
};
Prisma + GraphQL pattern
const resolvers = {
Query: {
order: async (_, { id }, { prisma, user }) => {
const order = await prisma.order.findUnique({
where: { id },
select: {
id: true,
status: true,
customerId: true,
totalAmount: true,
createdAt: true,
}
});
if (!order) return null;
if (order.customerId !== user?.id) throw new GraphQLError('Forbidden');
return order;
}
}
};
Example
User: Design a GraphQL schema and resolvers for a simple e-commerce API — products, orders, and customers. Include pagination, DataLoader for customers, and mutation error handling.
Expected output:
- Schema:
Product, Order, Customer, OrderConnection, UserError types
Query.orders with cursor pagination returning OrderConnection
Mutation.createOrder returning CreateOrderPayload with errors array
Order.customer resolver using DataLoader (not direct DB query)
createLoaders() function per request, batching customer lookups by ID
- Auth check: only authenticated users can view their own orders