Context

In the Node.js world, Context allows us to share data and state within the same request. In GraphQL, contexts allow data to be shared between multiple resolver functions and middleware for the same request.

A common use case is to store the identity of the current visitor in the context to be accessed in the resolver function and middleware.

Accessing a Context

In GQLoom, we access the context through the useContext() function.

GQLoom's useContext function is designed to reference React's useContext function. You can call the useContext function from anywhere within the resolver to access the context of the current request without explicitly passing the context function. Behind the scenes, useContext uses node.js' AsyncLocalStorage to pass the context implicitly.

Next, let's try to access the context in various places. We will use graphql-yoga as an adapter.

Accessing contexts in resolve functions

valibot
zod
import { query, resolver, useContext, weave } from "@gqloom/core" import { ValibotWeaver } } from "@gqloom/valibot" import * as v from "valibot" import { type YogaInitialContext, createYoga } from "graphql-yoga" import { createServer } from "http" const HelloResolver = resolver({ hello: query(v.string(), () => { const user = useContext<YogaInitialContext>().request.headers.get("Authorization") return `Hello, ${user ?? "World"}` }), }) const yoga = createYoga({ schema: weave(HelloResolver) }) createServer(yoga).listen(4000, () => { console.info("Server is running on http://localhost:4000/graphql") })

In the code above, we use the useContext function to get the Authorization header of the current request from the context and concatenate it with the Hello string. The useContext function accepts a generic parameter that specifies the type of the context, in this case we passed in the YogaInitialContext type.

Let's try to call this query:

curl -X POST http://localhost:4000/graphql -H "content-type: application/json" -H "authorization: Tom" --data-raw '{"query": "query { hello }"}'

You should get the following response:

{"data":{"hello":"Hello, Tom"}}

Accessing contexts in middleware

import { useContext, Middleware } from "@gqloom/core" import { type YogaInitialContext } from "graphql-yoga" function useUser() { const user = useContext<YogaInitialContext>().request.headers.get("Authorization") return user } const authGuard: Middleware = (next) => { const user = useUser() if (!user) throw new Error("Please login first") return next() }

In the code above, we created a custom hook called useUser that uses the useContext function to get the Authorization header of the current request from the context. We then created a middleware called authGuard which uses the useUser hook to fetch the user and throw an error if the user is not logged in.

To learn more about middleware, see middleware documentation.

Accessing contexts while validating inputs

valibot
zod

We can customize the validation or transformation in valibot and access the context directly within it.

import { query, resolver, useContext, weave } from "@gqloom/core" import { ValibotWeaver } } from "@gqloom/valibot" import * as v from "valibot" import { type YogaInitialContext, createYoga } from "graphql-yoga" import { createServer } from "http" async function useUser() { const authorization = useContext<YogaInitialContext>().request.headers.get("Authorization") const user = await UserService.getUserByAuthorization(authorization) return user } const HelloResolver = resolver({ hello: query(v.string(), { input: { name: v.pipeAsync( v.nullish(v.string()), v.transformAsync(async (value) => { if (value != null) return value const user = await useUser() return user }) ), }, resolve: () => `Hello, ${name}`, }), }) const yoga = createYoga({ schema: weave(HelloResolver) }) createServer(yoga).listen(4000, () => { console.info("Server is running on http://localhost:4000/graphql") })

In the code above, we use useUser() in v.transformAsync to get the user information in the context and return it as the value of name.

Memorization

Consider that we access the user through the following custom function:

async function useUser() { const authorization = useContext<YogaInitialContext>().request.headers.get("Authorization") const user = await UserService.getUserByAuthorization(authorization) return user }

We may execute some expensive operations in useUser(), such as fetching user information from the database, and we may also call it multiple times in the same request. To avoid the extra overhead of multiple calls, we can use memoization to cache the results and reuse them in subsequent calls.

In GQLoom, we use the createMemoization function to create a memoized function. A memoization function caches its results in the context after the first call and returns the cached results directly in subsequent calls. That is, the memoized function will only be executed once in the same request, no matter how many times it is called.

Let's memoize the useUser() function:

import { createMemoization } from "@gqloom/core" const useUser = createMemoization(async () => { const authorization = useContext<YogaInitialContext>().request.headers.get("Authorization") const user = await UserService.getUserByAuthorization(authorization) return user })

As you can see, we simply wrap the function in the createMemoization function. We can then call useUser() from anywhere within the resolver without worrying about the overhead of multiple calls.

Accessing Resolver Payload

In addition to the useContext function, GQLoom provides the useResolverPayload function for accessing all parameters in the resolver:

  • root: the previous object, not normally used for fields on the root query type;

  • args: the arguments provided for the field in the GraphQL query;

  • context: the context object shared across parser functions and middleware;

  • info: contains information about the current resolver call, such as the path to the GraphQL query, field names, etc;

  • field: the definition of the field being resolved by the current resolver;

Using Contexts across Adapters

In the GraphQL ecosystem, each adapter provides a different context object, and you can learn how to use it in the Adapters chapter: