extending graphql schema definitions with apollo

2020-04-03

 | 

~3 min read

 | 

471 words

When defining a GraphQL schema, it can be useful to define it in multiple files to keep it manageable.

./src/index.js
import { buildSchema } from 'graphql'
import { schemaToTemplateContext } from 'graphql-codegen-core'
import { loadTypeSchema } from '../../../utils/schema'
import { mockServer } from 'graphql-tools'

const root = `
  schema {
    query: Query
    mutation: Mutation
  }
`

const typeSchemas = await Promise.all(
  ['product', 'user', 'coupon'].map(loadTypeSchema))

typeDefs = root + typeSchemas.join(' ')
schema = schemaToTemplateContext(buildSchema(typeDefs))

typeSchemas is then the resolved promise of passing our three strings product, user, and coupon, into loadTypeSchema.

The loadTypeSchema is:

./utils/schema
import fs from 'fs'
import path from 'path'

export const loadTypeSchema = type =>
  new Promise((resolve, reject) => {
    const pathToSchema = path.join(
      process.cwd(),
      `src/types/${type}/${type}.gql`
    )
    fs.readFile(pathToSchema, { encoding: 'utf-8' }, (err, schema) => {
      if (err) {
        return reject(err)
      }

      resolve(schema)
    })
  })

This is a function that takes a type (which is our string), and then uses Node’s fs module to attempt to read it.

Let’s look at the product schema in more detail to understand how this works.

product.gql
enum ProductType {
  GAMING_PC
  BIKE
  DRONE
}

enum BikeType {
  KIDS
  MOUNTAIN
  ELECTRIC
  BEACH
}

type Product {
  name: String!
  price: Float!
  image: String!
  type: ProductType!
  description: String
  liquidCooled: Boolean
  bikeType: BikeType
  range: String
  createdBy: User!
}

input NewProductInput {
  name: String!
  price: Float!
  image: String!
  type: ProductType!
  description: String
  liquidCooled: Boolean
  bikeType: BikeType
  range: String
}

input UpdateProductInput {
  name: String
  price: Float
  image: String
  description: String
  liquidCooled: Boolean
  bikeType: BikeType
  range: String
}

extend type Query {  product(id: ID!): Product
  products: [Product]
}

extend type Mutation {  newProduct(input: NewProductInput): Product
  updateProduct(id: ID, input: UpdateProductInput): Product
  removeProduct(id: ID, input: UpdateProductInput): Product
}

So what’s happening?

Starting with our root, we scaffold out a schema that includes Query and Mutation objects, but nothing’s defined.

We build up the schema for both Query and Mutation by mapping over our array of strings.

Each one has a file like our products.gql which is then used to extend the schema thanks to the helper function loadTypeSchema (notice that it’s extend type Query, not type Query).

Each of these .gql files is then appended to the root which is then passed into the buildSchema function that is imported from graphql.

The important part here is that because we’re pulling multiple .gql files together, if they include new Query or Mutation objects, we need to extend them.

If we don’t extend, we’ll get a syntax error as we try to overwrite the Query and Mutation objects.

      at syntaxError (node_modules/graphql/error/syntaxError.js:15:10)
      at Parser.unexpected (node_modules/graphql/language/parser.js:1463:41)
      at Parser.parseDefinition (node_modules/graphql/language/parser.js:157:16)
      at Parser.many (node_modules/graphql/language/parser.js:1518:26)
      at Parser.parseDocument (node_modules/graphql/language/parser.js:111:25)
      at Object.parse (node_modules/graphql/language/parser.js:36:17)
      at Object.buildSchemaFromTypeDefinitions (node_modules/graphql-tools/src/generate/buildSchemaFromTypeDefinitions.ts:37:19)
      at mockServer (node_modules/graphql-tools/src/mock.ts:42:16)
      at Object.<anonymous> (src/types/product/__tests__/product.type.spec.js:163:22)

So, while this approach requires a little more setup up front, it has the benefit of enabling composable grpahql schemas.


Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!