TypeScript Resolvers
This plugin generates types for resolve functions.
Installation
$ yarn add @graphql-codegen/typescript-resolvers
Usage
Run graphql-codegen
as usual:
schema: schema.json
generates:
./src/resolvers-types.ts:
plugins:
- typescript
- typescript-resolvers
Import the types from the generated file and use in the resolver:
import { QueryResolvers } from './resolvers-types';
export const resolvers: QueryResolvers = {
myQuery: (root, args, context) => {},
};
This will make the resolver fully typed and compatible with typescript compiler, including the handler's arguments and return value.
Generated resolvers can be passed directly into graphql-tools makeExecutableSchema
function.
Configuration
ScalarsMap
)
scalars (Extends or overrides the built-in scalars and custom GraphQL scalars to a custom type.
Usage Example
config:
scalars:
DateTime: Date
JSON: { [key: string]: any }
NamingConvention
, default value: change-case#pascalCase
)
namingConvention (Allow you to override the naming convention of the output. You can either override all namings, or specify an object with specific custom naming convention per output. The format of the converter must be a valid module#method
. Allowed values for specific output are: typeNames
, enumValues
. You can also use "keep" to keep all GraphQL names as-is. Additionally you can set transformUnderscore
to true
if you want to override the default behaviour, which is to preserves underscores.
Usage Example: Override All Names
config:
namingConvention: change-case#lowerCase
Usage Example: Upper-case enum values
config:
namingConvention:
typeNames: change-case#pascalCase
enumValues: change-case#upperCase
Usage Example: Keep
config:
namingConvention: keep
Usage Example: Remove Underscores
config:
namingConvention:
typeNames: change-case#pascalCase
transformUnderscore: true
string
, default value: ""
)
typesPrefix (Prefixes all the generated types.
Usage Example: Add "I" Prefix
config:
typesPrefix: I
boolean
, default value: false
)
skipTypename (Does not add __typename to the generated types, unless it was specified in the selection set. in the selection set.
Usage Example
config:
skipTypename: true
boolean
, default value: false
)
nonOptionalTypename (Automatically adds __typename
field to the generated types, even when they are not specified in the selection set, and makes it non-optional
Usage Example
config:
nonOptionalTypename: true
boolean
)
addUnderscoreToArgsType (Adds _
to generated Args
types in order to avoid duplicate identifiers.
Usage Example: With Custom Values
config:
addUnderscoreToArgsType: true
string
)
contextType (Use this configuration to set a custom type for your context
, and it will effect all the resolvers, without the need to override it using generics each time. If you wish to use an external type and import it from another file, you can use add
plugin and add the required import
statement, or you can use a module#type
syntax.
Usage Example: Custom Context Type
plugins
config:
contextType: MyContext
Usage Example: Custom Context Type
plugins
config:
contextType: ./my-types#MyContext
string
)
rootValueType (Use this configuration to set a custom type for the rootValue
, and it will effect resolvers of all root types (Query, Mutation and Subscription), without the need to override it using generics each time. If you wish to use an external type and import it from another file, you can use add
plugin and add the required import
statement, or you can use both module#type
or module#namespace#type
syntax.
Usage Example: Custom RootValue Type
plugins
config:
rootValueType: MyRootValue
Usage Example: Custom RootValue Type
plugins
config:
rootValueType: ./my-types#MyRootValue
Object
)
mappers (Replaces a GraphQL type usage with a custom type, allowing you to return custom object from your resolvers. You can use both module#type
and module#namespace#type
syntax.
Usage Example: Custom Context Type
plugins
config:
mappers:
User: ./my-models#UserDbObject
Book: ./my-models#Collections#Book
string
)
defaultMapper (Allow you to set the default mapper when it's not being override by mappers
or generics. You can specify a type name, or specify a string in module#type
or module#namespace#type
format. The defualt value of mappers it the TypeScript type generated by typescript
package.
Usage Example: Replace with any
plugins
config:
defaultMapper: any
Usage Example: Custom Base Object
plugins
config:
defaultMapper: ./my-file#BaseObject
Usage Example: Wrap default types with Partial
You can also specify a custom wrapper for the original type, without overring the original generated types, use "T" to specify the identifier. (for flow, use $Shape<{T}>
)
plugins
config:
defaultMapper: Partial<{T}>
boolean
, default value: false
)
avoidOptionals (This will cause the generator to avoid using TypeScript optionals (?
), so all field resolvers must be implemented in order to avoid compilation errors.
Usage Example
generates:
path/to/file.ts:
plugins:
- typescript
- typescript-resolvers
config:
avoidOptionals: true
boolean
, default value: true
)
showUnusedMappers (Warns about unused mappers.
Usage Example
generates:
path/to/file.ts:
plugins:
- typescript
- typescript-resolvers
config:
showUnusedMappers: true
EnumValuesMap
)
enumValues (Overrides the default value of enum values declared in your GraphQL schema, supported in this plugin because of the need for integeration with typescript
package. See documentation under typescript
plugin for more information and examples.
string
, default value: Promise | T
)
resolverTypeWrapperSignature (Allow you to override resolverTypeWrapper
definition.
boolean
, default value: false
)
federation (Supports Apollo Federation
boolean
, default value: true
)
enumPrefix (Allow you to disable prefixing for generated enums, works in combination with typesPrefix
.
Usage Example: Disable enum prefixes
config:
typesPrefix: I
enumPrefix: false
boolean
, default value: false
)
immutableTypes (Generates immutable types by adding readonly
to properties and uses ReadonlyArray
.
Usage Example
generates:
path/to/file.ts:
plugins:
- typescript
- typescript-resolvers
config:
immutableTypes: true
boolean
, default value: false
)
useIndexSignature (Adds an index signature to any generates resolver.
Usage Example
generates:
path/to/file.ts:
plugins:
- typescript
- typescript-resolvers
config:
useIndexSignature: true
boolean
, default value: false
)
noSchemaStitching (Disables Schema Stitching support
The default behavior will be reversed in the next major release. Support for Schema Stitching will be disabled by default.
Usage Example
generates:
path/to/file.ts:
plugins:
- typescript
- typescript-resolvers
config:
noSchemaStitching: true
string
, default value: "graphql#GraphQLResolveInfo"
)
customResolveInfo (You can provide your custom GraphQLResolveInfo instead of the default one from graphql-js
Usage Example
generates:
path/to/file.ts:
plugins:
- typescript
- typescript-resolvers
config:
customResolveInfo: ./my-types#MyResolveInfo
How It Works
It adds the generic resolvers signature to the top of the file:
export type ResolverFn<TResult, TParent, TContext, TArgs> = (parent: TParent, args: TArgs, context: TContext, info: GraphQLResolveInfo) => Promise<TResult> | TResult;
export type StitchingResolver<TResult, TParent, TContext, TArgs> = {
fragment: string;
resolve: ResolverFn<TResult, TParent, TContext, TArgs>;
};
export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> = ResolverFn<TResult, TParent, TContext, TArgs> | StitchingResolver<TResult, TParent, TContext, TArgs>;
export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = (parent: TParent, args: TArgs, context: TContext, info: GraphQLResolveInfo) => AsyncIterator<TResult> | Promise<AsyncIterator<TResult>>;
export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = (parent: TParent, args: TArgs, context: TContext, info: GraphQLResolveInfo) => TResult | Promise<TResult>;
Then, it creates a default TypeScript resolvers signature, according to your GraphQL schema:
type Query {
allUsers: [User]
userById(id: Int!): User
}
type User {
id: Int!
name: String!
email: String!
}
Given the schema above, the output should be the following:
export interface QueryResolvers<ContextType = any, ParentType = Query> {
allUsers?: Resolver<Array<Maybe<User>>, ParentType, ContextType>;
userById?: Resolver<Maybe<User>, ParentType, ContextType, QueryUserByIdArgs>;
}
export interface UserResolvers<ContextType = any, ParentType = User> {
id?: Resolver<number, ParentType, ContextType>;
name?: Resolver<string, ParentType, ContextType>;
email?: Resolver<string, ParentType, ContextType>;
}
Let's talk what you get by default. Each resolver has 4 types for:
- an object that its parent resolved, we call it Parent.
- a value that resolver returns
- arguments
- a context
By default, the context is just an empty object {}
.
Arguments are generated based on schema, so you don't have to think about them.
Types of a parent and a returned value are pretty interesting. Given the example schema:
type Query {
allUsers: [User]
userById(id: Int!): User
}
type Profile {
bio: String
}
type User {
id: Int!
name: String!
email: String!
profile: Profile
}
By default:
- all fields in
Profile
expects to get aProfile
typed object as a parent - fields in
User
receives aUser
as a parent User.profile
returnsProfile
Query.userById
expects to returnsUser
typed object
This behavior might fit well with how your resolvers look like but in some cases you want to tweak it a bit.
Intergration with Apollo-Server
If you are using Apollo Server with TypeScript, note that you need to set useIndexSignature: true
in your config, in order to add a compatible index signature (more info).
Plugin Customization
The generated resolver's signature type can be overridden or modified by taking advantage of the generic deceleration feature.
Mappers - overwrite parents and resolved values
Remember the example we showed you, when the GraphQL type User
expects to be resolved by User
typed object? What if an object returned by Query.userById
has _id
property instead of id
. It breaks the default behavior. Thats' why we implemented mappers.
The idea behind Mappers is to map an interface to a GraphQL Type so you overwrite that default logic.
Let's define one. This is what Query.userById
passes on to the User
type:
// src/types.ts
export interface UserParent {
_id: string;
name: string;
email: string;
profile: {
bio: text;
};
}
This is how to map that interface with the according type:
# ...
generates:
path/to/file.ts:
config:
mappers:
User: ./types#UserParent
plugins:
- typescript-resolvers
Inside of config.mappers
we wired the User
with UserParent
from ./types.ts
. You can also define the interface inside of the config file or even use any
and other primitive types.
By typing
./types#UserParent
we tell codegen to create an import statement that includesUserParent
and gets it from./types
module Remember! The path have to be relative to the generated file.
Every other, not mapped type follows the default convention, so in order to overwrite it you can use the defaultMapper
option:
# ...
generates:
path/to/file.ts:
config:
defaultMapper: any
mappers:
User: ./types#UserParent
plugins:
- typescript-resolvers
Given the above config, every other type then User
will have any
as its parent and resolved value. We don't recommend to do it but it might be very helpful when you try to slowly map all types.
Custom Context Type
If you wish to use a custom type for your GraphQL context, yet you don't want to specify it each and every time you declare your resolvers, you can do it in the config file:
# ...
generates:
path/to/file.ts:
config:
contextType: ./context#MyContext
plugins:
- typescript-resolvers
export interface MyContext {
authToken: string;
}
The config above will make every resolver to have MyContext
as a context type.
import { QueryResolvers } from './resolvers-types';
export const resolvers: QueryResolvers = {
myQuery: (root, args, context) => {
const { authToken } = context;
// ...
},
};
Field resolvers will be modfied as well.