GraphQL Schema Stitching Modular Code

In previous blogs post, we got stuck at a problem and found a new tool to solve it i.e graphql-tools.

Let’s see from start what is graphql-tools and how we use it in our app.

First thing apollo graphql-tools recommends to use GQL type/query syntax directly and also to have schema and resolvers as separate.

So, to try this out first lets convert our user schema to GQL syntax and comment the todo schema from our app, only stick to user schema. And for now, we will merge entire schema to a single file to make it simple

First install using

$ npm install graphql-tools

Read this guide in detail https://www.apollographql.com/docs/graphql-tools/generate-schema.html

It explain’s everything clearly in detail how to write your schema and resolver.

In our case the first step looks like this

export default `
enum CountryAllowed {
    IN,
    US
}   
type Address {
    id: ID!,
    address: String,
    country: CountryAllowed,
    phone: String
}
 type Profile {
     id: ID!,
     name: String,
     address: [Address]
 }
 type Query {
     profile (id: Int!) : Profile
 }
`
import data from '../data'

export default {
    Query: {
        profile: async (_, { id }) => {
            let users = await data.getUsers()
            return users.find((user) => {
                return user.id == id
            })
        }
    },
    Profile: {
        address: async (user) => {
            console.log(user)
            let address = await data.getAddress()
            return address.filter((addr) => {
                return addr.user_id == user.id
            })
        }
    }
}

Much cleaner, i like it!

Here is the code for it https://github.com/nodeexcel/gql_tutorial/commit/2119ac6e45839f4c20e53e67d662fbdc63374048

Next, we need to split this into different files. Let’s see how to do that

Also before doing that there is a function addMockFunctionsToSchema({ schema }); do check it out. its really cool and helps you mock data easily. Read more about it here https://www.apollographql.com/docs/graphql-tools/mocking.html

To split our schema in multiple files we need to use a process called “schema stitching” read more about it here https://www.apollographql.com/docs/graphql-tools/schema-stitching.html

Before we move forward there is one things to understand, if we simply divide the schema/types into multiple files it won’t work directly.

What i mean is look at this code https://github.com/nodeexcel/gql_tutorial/commit/bbeea4557780742bebcc74bdfc6a1c4958db0ff2

In this we have simply divide schema in different files. This will give error saying “address” not found.

So to first, lets makes two different schema which a totally unrelated e.g “users” and “todo” and then we will stitch them.

So let’s make a todo.js file

import { makeExecutableSchema , addMockFunctionsToSchema } from "graphql-tools"

const schema = makeExecutableSchema({
    typeDefs: `
        type Todo {
            task: String,
            isComplete: Boolean,
            date: Int
        }
        type Query {
            todos: [Todo]
            todo (id: ID) : Todo
        }
    `
})

addMockFunctionsToSchema({
    schema: schema
})

export default schema

P.S. In this i am using “addMockFunctionsToSchema” simply because i don’t want to waste time writing resolver’s for this at this stage.

Now we can use “mergeSchema” to merge both the schema’s

export default mergeSchemas({
    schemas: [
        todoQuery,
        userQuery
    ],
    resolvers: resolvers
});

This way we are able to combine to separate schema

This the full source code https://github.com/nodeexcel/gql_tutorial/commit/b8e79d9f9b9f062364fcb82df66a3c8584e86007

P.S We used a “date” type in the above schema, but GQL doesn’t have any support for type like Date so i had to use Int. But there many open source libraries which provide such implementations like https://medium.com/open-graphql/top-5-graphql-scalars-3f8a38965b53

Next, let’s further try to break down the schema.

In this step we will break down user schema further into types and queries. See the code here https://github.com/nodeexcel/gql_tutorial/commit/a9d20ea517685e907d90b5b34d89b78b3edb0716

In this we are basically export “string” from different type files and combining them using typesDef: [] function

export default makeExecutableSchema({
    typeDefs: [addressType, profileType, profileQuery]
})

Next, lets put our resolves in different files, till now we have just been separating schema. Now there are two ways of doing this, either we can have schema and resolve in same file or make them in separate. For now i will have resolver/schema in same file

import data from '../data'


export const profileType = `
type Profile {
    id: ID!,
    name: String,
    address: [Address]
}`

export const resolver = {
    Profile: {
        address: async (user) => {
            console.log(user)
            let address = await data.getAddress()
            return address.filter((addr) => {
                return addr.user_id == user.id
            })
        }
    }
}


import { makeExecutableSchema } from "graphql-tools"

import { addressType } from "../types/address"

import { resolver as profileResolver, profileType } from "../types/profile"

const profileQuery = `
type Query {
    profile (id: Int!) : Profile
}
`

export const queryResolver = {
    Query: {
        profile: async (_, { id }) => {
            let users = await data.getUsers()
            return users.find((user) => {
                return user.id == id
            })
        }
    }
}

export default makeExecutableSchema({
    typeDefs: [addressType, profileType, profileQuery],
    resolvers: [queryResolver, profileResolver]
})

https://github.com/nodeexcel/gql_tutorial/commit/b7678ae77c31c85ceb9863f08cb559f3cc0b25e9

At there stage, there is one problem our queries/profile.js file also creates the full schema which is not correct. We need to refactor it further and also it’s best to have resolver’s in a separate file. it makes coding, debugging much better

Now the code looks much more organized

https://github.com/nodeexcel/gql_tutorial/commit/c2d28a36495ebe8f1934aaf4427b964e60f85004

Code Structure

At this point it’s important that we take a step back and see exactly where we are at.

  1. We have refactored our code to have types/ queries/ resolves/ schema/ data/ and stitched all of it together
  2. We have used a function ” addMockFunctionsToSchema ” which add’s mock resolver data. This is a very useful function to mock and generate test data
  3. All our data is in a separate file, we can add any kind of data store to it. Be it mysql, mongo or anything
  4. All our types/queries are defined in GQL as string only.
  5. We use the function ” makeExecutableSchema ” to combine these strings typeDef’s to make a schema
  6. We have two root level unrelated schema’s at this stage “todo” and “user”
  7. We use ” mergeSchemas ” to join these schema together. Important, mergeSchemas is used to merge executable schema’s but makeExecutableSchema can merge only string types/queries so there is an important difference to understand between the two.

Next’ lets see how to combine two separate schema

At, this stage let’s introduce another type called “User”. Till now i have be referring “profile” as “user” but from now “profile” and “user” will be separate.

//queries/user.js

export default `
type Query {
    user (id : Int!) : User
}
`

//types/User.js
export default `
type User {
    profile: Profile
}`

//schema/profile.js

import userQuery from "../queries/user"
import userType from "../types/user"

export default makeExecutableSchema({
    typeDefs: [addressType, profileType, userType, userQuery, profileQuery],
    resolvers: [profileResolver]
})

This is the standard process we have been following till now, but doing so will generate an error.

“Error: Type “Query” was defined more than once.”

The reason for this, is that in one single schema “string” we can only have one query type. We are merging all typeDef’s as only string at this, hence we can only have one query!

To solve this problem we need to use the “extend” keyword

//queries/user.js
export default `
extend type Query {
    user (id : Int!) : User
}
`

This means we are extending the existing query type, and not creating a new one. Rather the correct way to do this is to always use “extend” and define one primary empty query like this

https://github.com/nodeexcel/gql_tutorial/commit/96e998cdacfe35cdd15fc6cd5b04cc62658b6c7c

Let’s also define a resolve for user at this stage

//resolvers/user.js
import data from "../data"

export default {
    Query: {
        user: async (_, { id }) => {
            let users = await data.getUsers()
            let profile = users.find((user) => user.id == id)
            return {
                profile: profile
            }
        }
    }
}

https://github.com/nodeexcel/gql_tutorial/commit/0a81cf36f75c24183447aa3aa0bef58151925287

Next, let’s associate “user” with “todo”

What i mean is, currently we have two separate schema types “user’ and “todo” but if we try to establish a relationship between the two it will give an error.

e.g

if i make a schema like this

type User {
            profile: Profile,
            todos: [Todo]
        }

i get an error ‘Type “Todo” not found in document”

The reason is simple, Todo is not defined in the current schema.

I think, the best solution at this stage is to simple have all schema’s as string and merge then. I don’t see any reason to have two root level schema’s at this stage. So fix like this

https://github.com/nodeexcel/gql_tutorial/commit/eeb07cb3974f0bb082ae8f98729ed6e76a5ff1a6

Below is a well written blog which you can also follow to understand the above,

https://blog.apollographql.com/modularizing-your-graphql-schema-code-d7f71d5ed5f2

excellence-social-linkdin
excellence-social-facebook
excellence-social-instagram
excellence-social-skype