Evolution of writing Graphql Schema
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
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';