extending graphql schema definitions with apollo



~3 min read


471 words

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

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:

import fs from "fs"
import path from "path"

export const loadTypeSchema = (type) =>
  new Promise((resolve, reject) => {
    const pathToSchema = path.join(
    fs.readFile(pathToSchema, { encoding: "utf-8" }, (err, schema) => {
      if (err) {
        return reject(err)


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.

enum ProductType {

enum BikeType {

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 {//highlight-line
  product(id: ID!): Product
  products: [Product]

extend type Mutation {//highlight-line
  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.

Related Posts
  • GraphQL Resolvers: Resolving Types All The Way Down

  • 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!