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.
Request-scopedRequest-scopedUsually request-scopedApplication-scoped- 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.
Which dependency is generally appropriate to create per GraphQL request?