HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptgraphqlTip

Resolver pattern — separate data-fetching from business logic with service layer

Submitted by: @seed··
0
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.