Aug
18

Using GraphQL Scalars With Code Generator

posted on 18 August 2021 in programming

Here’s one that might be obvious for GraphQL veterans, but I wasted some time Googling for an answer that didn’t seem clear. I’m trying to use GraphQL Code Generator with a schema that was using GraphQL Scalars and received the error:

Failed to load schema from ./src/schema/**/*.ts:
Unknown type: "Date".

Following the docs for the Apollo Server Recipe I’m importing the custom scalars like this:

import { makeExecutableSchema } from "@graphql-tools/schema";
import { DateResolver, DateTypeDefinition } from "graphql-scalars";
...

const schema = makeExecutableSchema({
  typeDefs: [typeDefs, DateTypeDefinition],
  resolvers: {
    ...resolvers,
    Date: DateResolver,
  },
});

And in my schema file I’m using the Date scalar like this:

type Person {
    id: ID!
    ...
    birthDate: Date
    deathDate: Date
}

Now the problem is that the codegen tool parses GraphQL from files individually, so when it reads my schema file, it sees the Date object but doesn’t know what it is, because I’m not declaring the custom scalar until I stitch together the schemas later.

Require final schema

One solution to this to reconfigure how GraphQL Code Generator reads your schema. By deafult it uses an AST parser to read each file and extract any graphql strings. However we can reconfigure it to just require our file that exports a schema object.

schema:
  - ./src/schema/index.ts:
      noPluck: true
...

require:
  - ts-node/register

noPluck: true is telling GraphQL Code Generator to not try and parse the file for a GraphQL string, but just require the file and find the schema export. Note that as I’m using TypeScript we need to tell codegen how to read TypeScript, by requiring ts-node.

With this solution, the watch feature doesn’t work because it only watches the index.ts file for changes, not all our schema files, so we need to tell it what to watch:

graphql-codegen --watch "src/schema/**/*.ts"

This worked well until I ran into an issue with my development process. Becuase I’m keeping resolvers in the same file as my schema (following this modular GraphQL design pattern), when I change a schema and update the resolver, the build fails because the code is using outdated generated TypeScript types, and because the build fails the codegen tool can’t run to generate the types I need. A solution would be to update the schema first, and ensure the codegen tool runs before updating any TypeScript. But there’s a simpler solution.

Define the custom scalar in every schema

Having now realised the benefit of using AST parsing for the GraphQL Code Generator is that my TypeScript code doesn’t need to be valid, I gave in to a much easier way, just declare the custom scalar in each schema file.

scalar Date
...

The downside of this approach is needing to remember to declare each scalar that you use, I’d prefer to just import { DateTypeDefinition } from "graphql-scalars";, but it seems a small price to pay.

I also noticed that the generated types don’t have the code docs from graphql-scalars like with the required approach above. e.g. this comment is now missing:

export type Scalars = {
  Boolean: boolean;
  Int: number;
  Float: number;
  /** A date string, such as 2007-12-03, compliant with the `full-date` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
  Date: any;
};