patterntypescriptgraphqlTip
Resolver pattern — separate data-fetching from business logic with service layer
Viewed 0 times
resolverservice layerarchitecturebusiness logicseparation of concernstestable
Problem
Putting all database queries, validation, and business rules directly inside resolver functions creates untestable, duplicated code. The same logic is re-implemented when accessed from REST endpoints or background jobs.
Solution
Resolvers should be thin orchestrators. Extract business logic into service classes or functions that are independently testable.
// services/user.service.ts
export class UserService {
async createUser(input: CreateUserInput) {
if (await this.emailExists(input.email)) {
throw new Error('Email already in use');
}
return this.db.user.create({ data: input });
}
}
// resolvers/user.resolver.ts
const Mutation = {
createUser: (_root, { input }, ctx: Context) =>
ctx.services.user.createUser(input),
};Why
Services are plain functions/classes with no GraphQL coupling. They can be unit tested without spinning up a GraphQL server and reused from multiple transports.
Gotchas
- Resist putting authorization logic in resolvers — put it in services or middleware so it applies everywhere
- Don't pass the entire GraphQL context into services; pass only what they need to stay decoupled
- Resolver errors should be caught and wrapped into GraphQL-safe errors (use GraphQL Errors with extensions)
Context
Structuring GraphQL server codebases for maintainability
Revisions (0)
No revisions yet.