Evolution of writing Graphql Schema

Dilip Kumar
5 min readAug 4, 2018

Schema is heart of GraphQL based application. Since the inception of Graphql, writing of graphql schema has evolved. Recently when I started learning GraphQL, I noticed many patterns of schema. That confused me a lot. This post is going to talk about writing schema to explain the evolution for server as well as client.

GraphQL Schema at Server Side

Writing GraphQL Schema as Javascript

Facebook first developed “graphql” javascript module (also called as graphql.js)to implement Graphql specification. GraphQL Js (i.e. graphql javascript module) provides library to write schema in Javascript language. Following is sample example.

import { GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLInt } from 'graphql'var UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
name: { type: GraphQLString },
email: { type: GraphQLString },
age: { type: GraphQLInt }
})
});
const QueryRootType = new GraphQLObjectType({
name: 'RootQuery',
fields: () => ({
getUser: {
type: UserType,
resolve: () => ({
name: 'Dilip',
email: 'dilip.kumar2k6@gmail.com',
age: 35
})
},
})
});
// This is the schema declaration
const appSchema = new GraphQLSchema({
query: QueryRootType
});

This is very verbose way to write schema. This also allows to write resolver with schema which leads to poor code maintainability.

buildSchema over GraphQLSchema

“graphql” module was enhanced to build schema from plain text to keep it less verbose. Following is same schema written in GraphQL Schema Definition Language(SDL) using buildSchema by separating out resolver as well.

import { buildSchema } from 'graphql';// Construct a schema, using GraphQL schema language
const typeDefs = buildSchema(`
type User {
name: String
email: String
age: Int
}
type Query {
getUser: User
}
`);
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
getUser: () => ({
name: 'Dilip',
email: 'dilip.kumar2k6@gmail.com',
age: 35
}
),
},
};

This style is better compare to previous one. Writing schema for big project is still not manageable.

In last few years, Apollo team from Meteor started working on GraphQL. Meteor group was always known for best developer experience. Apollo team came up many modules and documentation to ease the development of application using GraphQL.

Use of graphql-tools for writing GraphQL Schema

graphql-tools was developed on the foundation of graphql . It made easier to write schema. Following is same schema written in new way.

import { makeExecutableSchema } from 'graphql-tools';// Construct a schema, using GraphQL schema language
const typeDefs = `
type User {
name: String
email: String
age: Int
}
type Query {
getUser: User
}
`;
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
getUser: (root, args, context) => {
return {
name: 'Dilip',
email: 'dilip.kumar2k6@gmail.com',
age: 35,
};
},
},
};
// GraphQL.js schema object as "schema"
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});

This helped to write schema separately. However still it was not possible to split schema into individual files for readability and maintainability.

Use of graphql-import to write Schema in separate file

Prisma from apollo graphql team developed graphql-import module to load schema from individual files. Same schema now can be written in different files as below.

User schema is defined in user.graphql file.

# users/user.graphql file
type User {
name: String
email: String
age: Int
}

Root schema is defined in index.graphql file.

# index.graphql file
import User from './user/User.graphql'
type Query {
getUser: User
}

Load index.graphql to create schema .

// index.js
const path = require('path');
const { makeExecutableSchema } = require('graphql-tools');
const { importSchema } = require('graphql-import');
// Load GraphQL schema from files
const typeDefs = importSchema(path.join(__dirname, './index.graphql'));
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
getUser: (root, args, context) => {
return {
name: 'Dilip',
email: 'dilip.kumar2k6@gmail.com',
age: 34,
};
},
},
};
// Put together a schema
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});

GraphQL Query for React Client

So far we have discussed the Schema for server side. Let’s now discuss the Query needed at client side to call GraphQL API. We will use React client for this discussion.

Use of AST to write Query for React Client

Abstract Syntax Tree AST is needed to call GraphQL API. For example, following is AST to call API

{
"kind": "Document",
"definitions": [
{
"kind": "OperationDefinition",
"operation": "query",
"name": null,
"variableDefinitions": null,
"directives": [],
"selectionSet": {
"kind": "SelectionSet",
"selections": [
{
"kind": "Field",
"alias": null,
"name": {
"kind": "Name",
"value": "getUser",
"loc": {
"start": 4,
"end": 11
}
},
"arguments": [],
"directives": [],
"selectionSet": {
"kind": "SelectionSet",
"selections": [
{
"kind": "Field",
"alias": null,
"name": {
"kind": "Name",
"value": "name",
"loc": {
"start": 18,
"end": 22
}
},
"arguments": [],
"directives": [],
"selectionSet": null,
"loc": {
"start": 18,
"end": 22
}
},
{
"kind": "Field",
"alias": null,
"name": {
"kind": "Name",
"value": "email",
"loc": {
"start": 27,
"end": 32
}
},
"arguments": [],
"directives": [],
"selectionSet": null,
"loc": {
"start": 27,
"end": 32
}
},
{
"kind": "Field",
"alias": null,
"name": {
"kind": "Name",
"value": "age",
"loc": {
"start": 37,
"end": 40
}
},
"arguments": [],
"directives": [],
"selectionSet": null,
"loc": {
"start": 37,
"end": 40
}
}
],
"loc": {
"start": 12,
"end": 44
}
},
"loc": {
"start": 4,
"end": 44
}
}
],
"loc": {
"start": 0,
"end": 46
}
},
"loc": {
"start": 0,
"end": 46
}
}
],
"loc": {
"start": 0,
"end": 46
}
}

Obviously this is very verbose and not at all developer friendly. To make easier for developer, Facebook team has developed relay module to write Query as string. Apollo team has also developed graphql-tag module to write Query as string.

Use Relay to write Query for React Client

react-relay module provide graphql function which takes query as string template and return AST needed for GraphQL API.

import {graphql} from 'react-relay';graphql`
query {
getUser {
name
email
age
status
}
}
`;

Use of Apollo graphql-tag to write Query for React client

graphql-tag module developed by Apollo team provides gql function which takes Query as string and return AST needed for GraphQL API.

import gql from 'graphql-tag';

const query = gql`
{
getUser {
name
email
age
status
}
}
`

graphql-tag compiles Query string into AST at run time. It caches previous parsed Query into simple dictionary. If you call gql on same Query multiple times, it doesn’t waste time parsing it again.

Use of babel-plugin-graphql-tag to compile at build time

GraphQL queries can be compiled at build time using babel-plugin-graphql-tag. Pre-compiling queries decreases the script initialization time and reduces the bundle size by potentially removing the need for graphql-tag at runtime.

Webpack preprocessing with graphql-tag/loader

It saves GraphQL ASTs processing time on client-side and enable queries to be separated from script over .graphql files.

webpack.config

loaders: [
{
test: /\.(graphql|gql)$/,
exclude: /node_modules/,
loader: 'graphql-tag/loader'
}
]

query.graphql

getUser {
name
email
age
status
}

In React component

import query from './query.graphql';

--

--