Resolvers - serlo/documentation GitHub Wiki

What are Resolvers?

Resolver is a term from the Apollo Server universe. It is basically a manual for how to fill the fields in a schema and thus allows the api to retrieve and manipulate data from whatever datasource is specified. Apollo Server has very good documentation and provides an easy-to-follow explanation in this article.

How we use Resolvers

Since we use TypeScript in this project, it was necessary to define the type of the resolver in the past. We have found this to be very confusing for programmers new to the api. Thus we created a way to get around this problem and created type helpers for resolvers. This allows an automatic generation of the resolver types, gets rid of an extra step and hopefully facilitates a better experience for new-comers. The type helpers are listed in the following.

The Resolvers type helper

~/types exposes a type Resolvers which includes all resolver objects and functions of all GraphQL objects and properties. So you can always write:

import { Resolvers } from '~/types'

export const resolvers: Resolvers = {
  ...
}

With this type you get basic type checking (I.e. Do your resolver functions and objects have the right name? Do you return the right model type?). However, this type does not check whether you have defined all necessary resolver functions. In order to achieve more type safety you can use one of the following type helpers.

Sidenote: You can always use Resolvers regardless of what kind of resolver you want to implement. Use it when our type system confuses you! (For example when you just started in this project or when you are new to TypeScript). We can always narrow the type down to a better type in the future.

The Queries<P> type helper

For implementing a query endpoint you can use Queries<...>. It takes the properties you want to implement as an argument. The argument needs to be given as a string or a union of strings (in case of more than one property). An example:

import { Queries } from '~/internals/graphql'

export resolvers: Queries<"activeAuthors" | "activeDonors" | "activeReviewers"> = {
  Query: {
    async activeAuthors(_parent, args, { dataSources }) { ... },
    async activeDonors(_parent, args, { dataSources }) { ... },
    async activeReviewers(_parent, args, { dataSources }) { ... },
  }
}

The Mutations<M> type helper

Following this best practice), we use namespaces for mutations. As an illustration, let's say in GraphQL we have something like:

type Mutation {
  thread: ThreadMutation!
}

type ThreadMutation {
  createComment(...): ThreadCreateCommentResponse
  createThread(...): ThreadCreateThreadResponse
}

In order to implement all resolver functions for a mutation namespace like the one above, you can use Mutations<"...">. Give the name of the namespace you want to implement (or the union of multiple namespaces) as an argument to this type helper. To implement the resolvers for the above schema you can write:

import { Mutations, createMutationNamespace } from '~/internals/graphql'

export resolvers: Mutations<"thread"> = {
  Mutations: {
    thread: createMutationNamespace()
  },
  ThreadMutation: {
    async createComment(_parent, args, { dataSources }): { ... },
    async createThread(_parent, args, { dataSources }): { ... },
  }
}

Now TypeScript will check whether you have implemented all necessary resolver functions in the mutation namespace.

The TypeResolvers<T> type helper

If you want to implement all resolver functions for a certain GraphQL type you can use the TypeResolvers<T> type helper. It will check whether you provided a resolver function for all properties of the GraphQl type which are not covered by the model type. As an argument you need to provide the GraphQL type in `~/types':

import { TypeResolvers } from '~/internals/graphql'
import { ArticleRevision } from '~/types'

export resolvers: TypeResolvers<ArticleRevision> = {
  ArticleRevision: {
    async author(parent) { ... },
    async threads(parent) { ... },
  }
}

Sidenote: This is the only type helper, for which you need to provide the GraphQL type and not its name to the type helper. This is because we miss a feature that allows us to get the GraphQL type when its name is given.

The InterfaceResolvers<I> type helper

In order to implement the __resolveType() resolver function of a GraphQl union or interface you can use the InterfaceResolvers<"..."> type helper. You need to provide the name of the interface as an argument, as you can see here:

import { InterfaceResolvers } from '~/internals/graphql'

export const resolvers: InterfaceResolvers<"AbstractUuid"> = {
  AbstractUuid: {
    __resolveType(...) { ... }
  }
}

Combine multiple resolvers

It is often the case that you do want to combine multiple of the above resolver types. This is possible with the & type operator. E.g. for ~/schema/thread/resolvers.ts we can define:

export const resolvers: InterfaceResolvers<'ThreadAware'> &
  Mutations<'thread'> &
  TypeResolvers<Thread> &
  TypeResolvers<Comment> = {
    ...
}

In this example we want to implement the resolver functions for the interface ThreadAware (= InterfaceResolvers<'ThreadAware'>), the resolver functions for the GraphQL types Thread and Comment (= TypeResolvers<Thread> & TypeResolvers<Comment>) and all mutation resolver functions in the namespace thread (= Mutations<'thread'>).

⚠️ **GitHub.com Fallback** ⚠️