Documentation
Building your API (server)
Context

context

You might have noticed that in our load and resolve functions that we don't have the request parameters readily available to us, we might however need them to check i.e. if a user is authorized, ... This is done through the context object which will be carried around throughout the whole request lifecycle.

Defining context

NOTE: If you're using Next.js, please see below on how to define context.

If we need context we need to add an _context.ts file at the root and export a getContext function:

import type { GetContext, InitialContext } from 'fuse'
 
// Ensures static typing throughout our fuse schema
declare module 'fuse' {
  export interface UserContext {
    ua: string | null
  }
}
 
export const getContext = (
  ctx: InitialContext,
): GetContext<{ ua: string | null }> => {
  return {
    ua: ctx.request.headers.get('user-agent'),
  }
}

Now our ctx attribute in our resolvers will be statically typed and this function will execute on every invocation.

Next.js

Context has to be defined a bit differently in Next.js than otherwise. Using it in your API, however, stays the same.

API-Route

By default we will expose params and headers on the context object. If you want to expose more data you can do so by adding it to the function invocation of your API handler.

import { createAPIRouteHandler } from 'fuse/next'
 
createAPIRouteHandler<{ userAgent: string }>({
  context: (ctx) => {
    return {
      userAgent: ctx.headers.get('user-agent') || 'unknown',
    }
  }
})

Now the userAgent will be available to all of our GraphQL resolvers!

Server components

In server-components we don't have the headers available by default when using the execute function, this to avoid automatically opting people into dynamic functions (opens in a new tab). We'll have to pass these in with the second argument of our execute function.

import { headers } from 'next/headers'
import { createAPIRouteHandler } from '@/fuse/server'
 
execute({
  query: x,
  variables: {},
  context: () => {
    return {
      userAgent: headers().get('user-agent') || 'unknown'
    }
  } 
})

This means that if you use context.headers or a related property in your resolvers that you will need to define this yourself if your executed document would tap into those resolvers.

Static typing

You can ensure that your Context is typed in both your server as well as your client code by overriding the global type in fuse:

// Important so the surrounding types won't override
import 'fuse'
 
declare module 'fuse' {
  // This basically means that the `context` needs to define
  // a userId
  export interface UserContext {
    userId: string | null
  }
}

Using context in your API

In our fields there are two opportunities we have to use context the first being during resolve and the second during load, below you can find an example of both.

import { node, addQueryFields, AuthenticationError } from 'fuse'
 
const UserNode = node<UserSource>({
  name: 'User',
  load: async (ids, ctx) => ctx.isAdmin ? getUsers(ids) : [],
  fields: (t) => ({
    name: t.exposeString('name'),
    // rename to camel-case
    avatarUrl: t.exposeString('avatar_url'),
    // Add an additional firstName field
    firstName: t.string({
      resolve: (user) => user.name.split(' ')[0],
    }),
  }),
})
 
addQueryFields((t) => ({
  me: t.list({
    type: UserNode,
    nullable: false,
    args: {
      offset: t.arg.int({}),
      limit: t.arg.int(),
      filter: t.arg({ type: FilterInput }),
    },
    resolve: async (_, args, context) => {
      if (context.userId) {
        return context.userId
      }
 
      throw new AuthenticationError('You must be logged in.')
    },
  }),
}))