Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62
Subscribe to my email list now at http://jauyeung.net/subscribe/
We can create a simple GraphQL server with Express. To do this, we need the express-graphql
and graphql
packages.
In this article, we’ll look at how to add types that we can use to build a schema with the graphql
package.
Constructing Types
We can construct a schema programmatically with the GraphQLSchema
constructor that comes with the graphql
package.
Instead of defining Query
and Mutation
types using the schema language, we can create them as separate object types.
For example, we can write the following to create a type with the graphql.GraphQLObjectType
constructor to create an object type programmatically:
const express = require('express');
const graphqlHTTP = require('express-graphql');
const graphql = require('graphql');
const userType = new graphql.GraphQLObjectType({
name: 'User',
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
}
});
let users = {
'1': {
id: '1',
name: 'Jane'
}
}
const queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: userType,
args: {
id: { type: graphql.GraphQLString }
},
resolve: (_, { id }) => {
return users[id];
}
}
}
});
const schema = new graphql.GraphQLSchema({ query: queryType });
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(3000, () => console.log('server started'));
In the code above, we created the userType
GraphQL data type by writing:
const userType = new graphql.GraphQLObjectType({
name: 'User',
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
}
});
The name
field defines the name of our type and the fields
object has the fields that we include with the type. We defined id
and name
both to have type String
.
Then we define our Query
type with:
const queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: userType,
args: {
id: { type: graphql.GraphQLString }
},
resolve: (_, { id }) => {
return users[id];
}
}
}
});
In the code above, we defined the name
of the type to be Query
. The fields
we include is the user
field, which is of type User
that we defined above.
Also, we specified that we have the String id
as the argument with the args
property.
Finally, we have a resolve
property with the resolver to return what we want to return.
In this case, we want to return the User
from the users
object given the id
passed into the argument.
Then when we make the following query:
{
user(id: "1"){
id
name
}
}
We get back:
{
"data": {
"user": {
"id": "1",
"name": "Jane"
}
}
}
since we have the following in the users
object:
let users = {
'1': {
id: '1',
name: 'Jane'
}
}
We can do the same with mutations.
This is particularly useful is we want to create a GraphQL schema automatically from something else like a database schema. We may have a common format for something like creating and updating database records.
It’s also useful for implementing features like union types that don’t map to ES6 constructs.
GraphQLUnionType
We can create union types with the GraphQLUnionType
constructor.
To create a union type and use it in our app, we can use the GraphQLUnionType
constructor as follows:
const express = require('express');
const graphqlHTTP = require('express-graphql');
const graphql = require('graphql');
class Dog {
constructor(id, name) {
this.id = id;
this.name = name;
}
};
class Cat {
constructor(id, name, age) {
this.id = id;
this.name = name;
this.age = age;
}
};
const DogType = new graphql.GraphQLObjectType({
name: 'Dog',
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
}
});
const CatType = new graphql.GraphQLObjectType({
name: 'Cat',
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
age: { type: graphql.GraphQLInt },
}
});
const PetType = new graphql.GraphQLUnionType({
name: 'Pet',
types: [DogType, CatType],
resolveType(value) {
if (value instanceof Dog) {
return DogType;
}
if (value instanceof Cat) {
return CatType;
}
}
});
let pets = {
'1': new Dog('1', 'Jane'),
'2': new Cat('1', 'Jane', 11),
}
const queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
pet: {
type: PetType,
args: {
id: { type: graphql.GraphQLString }
},
resolve: (_, { id }) => {
return pets[id];
}
}
}
});
const schema = new graphql.GraphQLSchema({ query: queryType });
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(3000, () => console.log('server started'));
In the code above, we created the Dog
and Cat
classes to serve as models for our data.
Then we create the GraphQL Dog
and Cat
types as follows:
const DogType = new graphql.GraphQLObjectType({
name: 'Dog',
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
}
});
const CatType = new graphql.GraphQLObjectType({
name: 'Cat',
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
age: { type: graphql.GraphQLInt },
}
});
We defined the DogType
and CatType
constants to define the Dog
and Cat
object types.
Dog
has id
and name
fields and Cat
has id
, name
, and age
fields.
Then we defined the Pet
union type, which is a union of Dog
and Cat
as follows:
const PetType = new graphql.GraphQLUnionType({
name: 'Pet',
types: [DogType, CatType],
resolveType(value) {
if (value instanceof Dog) {
return DogType;
}
if (value instanceof Cat) {
return CatType;
}
}
});
Note that we have an array of types
and a resolveType
method instead of the resolve
method.
Then finally, we create our query type so that we can return a response to the user as follows:
const queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
pet: {
type: PetType,
args: {
id: { type: graphql.GraphQLString }
},
resolve: (_, { id }) => {
return pets[id];
}
}
}
});
The resolve
function gets the pets
entry by id
and returns it, and we specified that the type
we return is the PetType
.
Once we did that, we can make our query using inline fragments as follows:
{
pet(id: "1"){
__typename,
...on Dog {
id
name
}
...on Cat {
id
name
age
}
}
}
In the query above, we discriminated between the fields of Dog
and Cat
by using the ...on
operator. __typename
gets the type of the object returned.
With that query, we should get:
{
"data": {
"pet": {
"__typename": "Dog",
"id": "1",
"name": "Jane"
}
}
}
since we have a Dog
instance with key '1'
in pets
.
On the other hand, if we make a query for Pet
with ID 2 as follows:
{
pet(id: "2"){
__typename,
...on Dog {
id
name
}
...on Cat {
id
name
age
}
}
}
We get:
{
"data": {
"pet": {
"__typename": "Cat",
"id": "1",
"name": "Jane",
"age": 11
}
}
}
since we have a Cat
instance as the object with key '2'
in pets
.
Conclusion
We can create types with GraphQLObjectType
constructor to create object types.
To create union types, we can use the GraphQLUnionType
, then we have to resolve the type in the resolveType
method by checking the type of the object and returning the right one.
We can query union types with inline fragments and check the type with __typename
.