Typescript types for resolvers

Hi all, how do you write the resolvers with typescript? I saw this example

const resolvers = {
  Query: {
    // Our resolvers can access the fields in contextValue
    // from their third argument
    currentUser: (parent, args, contextValue, info) => {
      return contextValue.dataSources.userApi.findUser(contextValue.token);
    },
  },
};

my question is: have the “parent”, “args”, “contextValue”, “info” types? In docs, there is nothing about it…But I think: “any” type will be a bad style…

Hey @Conny_Gu :wave:!

parent, args and contextValue you’ll need to type yourself since they are dependent on your schema and your setup.

parent
This value should be whatever you return from a parent resolver. For example, let’s say your schema looks something like this:

query {
  currentUser: User
}

type User {
  firstName: String
}

And that you’re trying to resolve User.firstName in this query:

query {
  currentUser {
    firstName
  }
}

In this case, the parent type should be equal to the TypeScript type returned from your currentUser resolver. Let’s call that TypeScript User. Your parent value would look something like this:

import { User } from './types.ts' // or whenever you store this type

const resolvers = {
  User: {
    firstName: (parent: User) => {
      // ...
    }
  }
}

In your example above, since you’re trying to resolve a top-level Query field, the value is going to depend on whether you pass a rootValue option to your ApolloServer constructor. If you’ve passed a value to rootValue, then parent is going to be the same type as rootValue. If not, then any or never will likely suit you just fine since you’re likely not going to use that value.

args

This type will depend on your schema and whether the field you’re trying to resolve takes arguments or not. If your field takes no arguments, any or never should work just fine here. If you do, the object type should match the types in your schema. for example:

# schema.graphql
type Query {
  posts(limit: Int!): [Post!]!
}
interface QueryPostsArgs {
  limit: number
}

const resolvers = {
  Query: {
    posts: (parent: any, args: QueryPostsArgs) => {
      // ...
    }
  }
}

contextValue
This type is going to depend on whether you pass a context function to the ApolloServer constructor. If you don’t pass context, then any or never is going to be ok here. If you do, the value should match what you return from that function. See the “Sharing Context” docs for an example of how you use context.

info
You can type this with the GraphQLResolveInfo imported from the graphql package.

import { GraphQLResolveInfo } from 'graphql';

Using your example above it will look something like this:

import { GraphQLResolveInfo } from 'graphql';
import { RootValue, ContextValue } from './types'; // or wherever you store these types

interface QueryCurrentUserArgs {
  // empty for now since I don't know if your schema takes args here
}

const resolvers = {
  Query: {
    // Our resolvers can access the fields in contextValue
    // from their third argument
    currentUser: (
      parent: RootValue, 
      args: QueryCurrentUserArgs, 
      contextValue: ContextValue, 
      info: GraphQLResolveInfo
    ) => {
      return contextValue.dataSources.userApi.findUser(contextValue.token);
    },
  },
};

Hope that helps!

1 Like

@Conny_Gu forgot to mention in my reply, you might also take a look at GraphQL Code Generator, specifically the TypeScript Resolvers Plugin’. This can help automate the types for your resolvers. Check out our docs on generating types to get some more info on this approach.

2 Likes

Hi @jerelmiller thanks very much for the answer. Currently I am trying to use the codegen, but somehow it always show the type error. I don’t know, where to ask questions for codegen, maybe I will make here another post…

@Conny_Gu check out the YouTube video embedded on that typescript-resolvers page. It had some useful information on some of the advanced features. In it I discovered that the mappers property is which allows you to tweak the parent type sent into the resolver. Perhaps that might be of use? You should also be able to use the contextType option to ensure the default context type is the context type you use for your app. Try these two options and see if that helps!

1 Like

@jerelmiller thanks very much for the instruction. I watched the video and tried a night, still can not figure out the the error of resolvers.

So I made a small example:
(I did this small example in a codesandbox: )

lets say a job board website with two entities: Company and Job. A Company can have multiple jobs, and job can have only one Company. The models looks like:

export interface ICompany extends Entity {
  id: string
  name: string
  description: string
}

export interface IJob extends Entity {
  id: string
  companyId: string
  title: string
  description: string
}

but my schema.graphql looks like this:

type Company {
  id: ID!
  name: String!
  jobs: [Job!]
}

type Job {
  id: ID!
  title: String!
  company: Company!
}

type Query {
  company(id: ID!): Company
}

Notice: the main difference between the models and the Grapqhl Types are: The model ICompany doesn’t have the “jobs” property, but type Company do have jobs.

What I am doing is according the video, added the mappers in config:

schema: "./schema.graphql"
generates:
  ./src/__generated__/resolvers-types.ts:
    plugins:
      - "typescript"
      - "typescript-resolvers"
    config:
      useIndexSignature: true
      mappers:
        Company: ../db#ICompany
        Job: ../db#IJob

However, the generated type Resolvers is always wrong. I have no idea, whats happened.

export const resolvers: Resolvers = {
  Query: {
     company: (_root, { id }) => DB_Company.findById(id),
   },

  ...

Error is:
Type ‘(_root: {}, { id }: RequireFields<QueryCompanyArgs, “id”>) => Promise<ICompany | undefined>’ is not assignable to type ‘Resolver<Maybe<ResolverTypeWrapper>, {}, any, RequireFields<QueryCompanyArgs, “id”>> | undefined’.

However, if I change the Resolver to any, than everything works fine.

Can you have a check and point out, what’s the problem of the Resolvers type?

Thanks for the code snippet! This helps!

Looking at your CodeSandbox, I actually think you have this setup correctly, the problem is that there are cases where you expect a field to be non-nullable, but the return value of your resolver might return undefined. For example, this resolver:

Job: {
  company: (job) => {
    return DB_Company.findById(job.companyId)
  },
},

I’m seeing the following error:

Type '(job: IJob) => Promise<ICompany | undefined>' is not assignable to type 'Resolver<ResolverTypeWrapper<ICompany>, IJob, any, {}> | undefined'.
  Type '(job: IJob) => Promise<ICompany | undefined>' is not assignable to type 'ResolverFn<ResolverTypeWrapper<ICompany>, IJob, any, {}>'.
    Type 'Promise<ICompany | undefined>' is not assignable to type 'ResolverTypeWrapper<ICompany> | Promise<ResolverTypeWrapper<ICompany>>'.
      Type 'Promise<ICompany | undefined>' is not assignable to type 'Promise<ICompany>'.typescript(2322)

Pay close attention to the last line and this becomes apparent:

      Type 'Promise<ICompany | undefined>' is not assignable to type 'Promise<ICompany>'.typescript(2322)

This is the return value that is incorrect from your resolver.

Here, your DB_Company.findById(id) might return undefined, but you’ve specified in your schema that its a non-null field. You can either make this a nullable field, or do something like this to fix the issue:

Job: {
  company: async (job) => {
    const company = await DB_Company.findById(job.companyId);
    
    if (!company) {
      throw new Error('Company not found');
    }
    
    return company;
  },
},

The error on Query.company is a bit more obscure. The issue here is that your resolver expect a return value of Maybe<ResolversTypes['Company']>, where Maybe is defined as type Maybe<T> = T | null. I see this particular resolver might return undefined, which is not compatible with null. You’ll either need to explicitly return null when the value is missing:

Query: {
    company: async (_root, { id }) => {
      const company = await DB_Company.findById(id);
      
      return company || null;
    }
  },

or you can update your codgen config to set the maybeValue to something that includes undefined:

# codgen.yml
generates:
  'path/to/file.ts':
     plugins: ['typescript']
     config:
       maybeValue: 'T | null | undefined'

I found both of these to clear up the type errors you’re seeing.


That Resolver type signature is a bit hard to read since it takes 4 generic values. The first 2 generic parameters are the ones that you care about the most. The first generic type is the return value of the resolver, and the second generic type is the parent type for the resolver (the type of the thing passed as the first argument to the resolver function).

Hope this helps!

1 Like

Hi @jerelmiller You are amazing!!! Thanks for the answer!!! I learned a lot from you!

1 Like

You’re very welcome! Glad I could help!