From harness-claude
Implements GraphQL resolvers with clean separation of data fetching, business logic, and response shaping. Useful for writing queries/mutations, debugging N+1 issues, and setting up context.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Implement resolvers with clean separation between data fetching, business logic, and response shaping
Implements GraphQL resolvers covering function signatures, field resolvers, context management, DataLoader batching, error handling, authentication, and testing strategies.
Creates GraphQL resolvers with step-by-step guidance, best practices, production-ready code, and validation for API development tasks including REST, OpenAPI, and authentication.
Guides GraphQL schema design, resolvers, DataLoader for N+1 prevention, federation, subscriptions, and Apollo/urql clients. Use for efficient APIs with complex data relationships.
Share bugs, ideas, or general feedback.
Implement resolvers with clean separation between data fetching, business logic, and response shaping
(parent, args, context, info). Use them intentionally — parent carries the result from the parent resolver, args contains the field arguments, context is the per-request shared state, info holds the AST and field metadata.const resolvers = {
Query: {
user: (_parent: unknown, args: { id: string }, context: Context) => {
return context.dataSources.users.findById(args.id);
},
},
};
// Good — resolver delegates to service
const resolvers = {
Mutation: {
cancelOrder: async (_parent, { input }, { dataSources, currentUser }) => {
const result = await dataSources.orders.cancel(input.orderId, input.reason, currentUser);
return {
order: result.order,
refundAmount: result.refund,
errors: result.errors,
};
},
},
};
// Bad — business logic in resolver
const resolvers = {
Mutation: {
cancelOrder: async (_parent, { input }, { db }) => {
const order = await db.query('SELECT * FROM orders WHERE id = $1', [input.orderId]);
if (order.status === 'SHIPPED') throw new Error('Cannot cancel shipped order');
// ... 50 lines of business logic
},
},
};
const resolvers = {
User: {
fullName: (user) => `${user.firstName} ${user.lastName}`,
orders: (user, _args, { dataSources }) => {
return dataSources.orders.findByUserId(user.id);
},
},
};
context in your server setup with authenticated user, data sources, and request-scoped services. Avoid putting the raw req/res objects in context — wrap what you need.const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => ({
currentUser: await authenticateToken(req.headers.authorization),
dataSources: {
users: new UserDataSource(db),
orders: new OrderDataSource(db),
},
logger: createRequestLogger(req),
}),
});
Organize resolvers by domain. Split resolvers into files matching your domain areas — user.resolvers.ts, order.resolvers.ts — then merge them using lodash.merge or a resolver merging utility.
Return promises, not awaited values, when possible. GraphQL execution handles promises natively. Returning the promise directly (without await) allows the executor to parallelize sibling field resolution.
Use the info argument sparingly. It provides the full query AST, useful for advanced optimizations (look-ahead to avoid over-fetching), but parsing it adds complexity. Prefer DataLoader for batching over manual info inspection.
Handle null propagation deliberately. If a field resolver throws or returns null for a non-null field, the error bubbles up. Consider wrapping risky resolvers in try-catch and returning null (for nullable fields) or structured errors (for mutation payloads).
Resolver chain execution: GraphQL resolves fields top-down, breadth-first within each level. The return value of a parent resolver becomes the parent argument of its child field resolvers. If a Query resolver returns { id: '1', name: 'Alice' }, the User.name field resolver receives that object as parent.
Default resolver: If no resolver is defined for a field, GraphQL uses the default resolver: parent[fieldName]. This means you only need explicit resolvers for fields that require computation, transformation, or separate data fetching.
Data source pattern (Apollo): Encapsulate data access in classes that extend DataSource. Each data source gets access to the request context and can implement caching, batching, and error handling independently.
Testing resolvers: Test resolvers by calling them directly with mocked context and args. Test the full GraphQL execution path separately with integration tests using executeOperation or server.executeOperation.
Common pitfalls:
parent object in a field resolver (shared reference across sibling fields)UserError types in mutationshttps://graphql.org/learn/execution/