15
Better Type Safety for your GraphQL resolvers with GraphQL Codegen
This article was published on 2020-05-17 by Dotan Simha @ The Guild Blog
If you use TypeScript to write your GraphQL schema implementation, you'll love the integration with GraphQL Codegen and typescript-resolvers
plugin.
This plugin allow you to easily have typings for your resolvers, with super flexible configuration that allow you to integrate it easily to your existing code, types and models.
That means that you can type-seal your code and have complete type-safety: your GraphQL resolvers will be typed (parent type, args, inputs, return value, context type), and you can use your own TypeScript type models so you can have type-safety all across your implementation, from API to database.
Having type check on your resolvers can help to improve your code quality, detect issues in build time (instead of runtime), and improve developer experience.
If you are already familiar with GraphQL Code Generator, you can skip this step.
Start by installing GraphQL Codegen and the relevant plugins:
yarn add @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers
Now, create codegen.yml
file with the following, make sure to point to your schema location:
schema: YOUR_SCHEMA_LOCATION_HERE
generates:
./resolvers-types.ts:
plugins:
- typescript
- typescript-resolvers
GraphQL Code Generator uses
graphql-tools
so you can point to your schema files, or/graphql
endpoint.
To run GraphQL Codegen, use: yarn graphql-codegen
(or, create a script for it if you wish). This should create a new file called resolvers-types.ts
in your codebase.
In this example, we'll use the following GraphQL schema and resolvers as reference. You can find a working live-demo of this part here.
This is a naive implementation of a GraphQL API, and we'll see how more advanced use cases could be implemented in the upcoming steps.
To get started with the generated files, import Resolvers
identifier from your generated file, and use it to type your resolvers object, for example:
Now, TypeScript engine will verify that object you returned, and you'll be able to see that if you'll change one of the fields, it will be type checked immediately:
Also, if you'll change your schema types and re-run the codegen (or use Watch Mode), it will re-generate the matching types and check your code again.
As your probably understood, the default behavior of typescript-resolvers
is using the base type generated by typescript
, that means, that your schema types and resolvers needs to match and have the same signature and structure.
But it's not always the case - because your GraphQL schema, in most cases, isn't the same as your models types - this is why we have mappers
configuration.
Models types are the way your data is being stored or represented behind the scenes. Think about User
object from example - in most cases, the representation of User
in your database (or any other downstream API) is different than the way your represent User
in your API. Sometimes it's because of security considerations, and sometimes because fields are internal and used only by you, and not by the consumers.
Those model types are the actual objects that you are usually using in your resolvers code. Those can be created manually (with a simple TypeScript type
, interface
or class
), or created automatically from your downstream APIs, database or any other data-source that you use in your app.
The way to tell codegen where are your models types are located is called mappers
.
To use mappers
configuration, we need first to setup a real type safety, and have models types for our actual objects.
Let's assumes that your backend is implemented this way, with some models types:
It means that now, your resolvers implementation needs to adjusted and handle the different data structure:
Noticed the errors? it caused by the fact that we don't have the appropriate mapping set yet. We need to tell GraphQL codegen that our schema types are different than the model types.
To do that, let's update codegen config with mappers
and add a mapping between a GraphQL type to a TypeScript type (and the file it's located in):
This way, GraphQL Codegen will use your custom models types in the generated output, instead of the default types, and your resolvers implementation will look like that:
Note that now you'll get autocomplete, type safety and a better connection between your GraphQL schema and your GraphQL resolvers:
typescript-resolvers
also supports replacing the context
type of your resolvers implementation. All you have to do, is to add this following to your codegen configuration:
schema: schema.graphql
generates:
./resolvers-types.ts:
config:
contextType: models#MyContextType
mappers:
User: ./models#UserModel
Profile: ./models#UserProfile
plugins:
- typescript
- typescript-resolvers
This will make sure to replace any
with MyContextType
, and you'll be able to access a fully-typed context
object in your resolvers.
A few notes that worth mentioning:
- You can use
mappers
on every GraphQL type, interface or a union. - Your resolvers' arguments (
args
) are also fully-typed, according to your schema definition. - The
parent
value is also fully typed, based on yourmappers
. - You can import your types from a node module package (
User: models-lib#UserType
). - You can also map to built-in language types (
DateType: Date
) - Aliasing the imports is also possible (
User: ./models#User as MyCustomUserType
)
You can also modify the default mapper (defaultMapper
) and allow partial resolution, this will allow you to return partial objects in every resolver (more info):
config:
useIndexSignature: true
defaultMapper: Partial<{T}>
For more advanced use-cases, you can find the complete plugin documentation here.
15