2019-12-16
|~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(
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.
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 {//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.
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!