Building a Clean GraphQL Context

The GraphQL context is an important dependency boundary between the request and resolver execution.

A mature context might contain:

{
  requestId,
  user,
  services,
  repositories,
  loaders,
  logger
}

For example:

const context = async ({ req }) => {
  const user = await authenticate(req);

  return {
    requestId: req.headers["x-request-id"] ?? crypto.randomUUID(),
    user,
    services,
    repositories,
    loaders: createLoaders(),
    logger: createRequestLogger()
  };
};

This creates a request-scoped environment for resolver execution.

A common mistake is creating expensive resources on every request unnecessarily. Database connection pools, for example, are generally application-level resources rather than something that should be opened and closed for every GraphQL operation.

On the other hand, DataLoader instances and request-specific authorization information are commonly scoped per request.

Think carefully about lifecycle:

Application lifetime
  → DB pool
  → configuration
  → reusable stateless services

Request lifetime
  → authenticated user
  → request ID
  → DataLoader cache
  → request logger

Correct lifecycle management is important for both performance and security.

Context Lifecycle
Request ID: Request-scoped
Identifies a single request across logs and downstream calls.
Authenticated user: Request-scoped
Represents the identity associated with the current request.
DataLoader: Usually request-scoped
Prevents cached values from accidentally crossing request or user boundaries.
Database pool: Application-scoped
A connection pool is generally reused rather than recreated per request.
Key Takeaways
  • Context should contain useful request-scoped dependencies.
  • Not every dependency belongs in request scope.
  • Database pools are generally application-scoped.
  • Request-specific DataLoader instances should normally not be globally shared.
Knowledge Check

Which dependency is generally appropriate to create per GraphQL request?