Creating an Express and GraphQL Server from Scratch

Written by Carlos Angarita, Software Engineer

GraphQL is a language designed for APIs that only loads the specific data each client requests, which is its main attraction. Initially created by Facebook, its growing ecosystem formed the GraphQL Foundation, a neutral home to the GraphQL community. As the title suggests, we are going to be implementing a GraphQL server in Node using Express to create an API for a shopping list. So, let's create our project!


    
mkdir GraphQLShopping
cd GraphQLShopping
npm init -y
npm install express express-graphql graphql
    
  

We’ve just created a new Node project and installed Express and GraphQL. Next, we need to create index.js, the entry point for our app and we need a route to call from our clients. The great thing about GraphQL is that you only need one route instead of the multitude of routes that we usually have in a REST API. This route receives a query with the structure of the data and returns a JSON object with the data in the requested structure. For this tutorial, we are going to create the /graphql route:


    
const express = require("express");
const graphqlHTTP = require("express-graphql");
const GraphQLSchema = require("graphql").GraphQLSchema;
const app = express();
app.use(
  "/graphql",
  graphqlHTTP({
    schema: new GraphQLSchema({}),
    graphiql: true
  })
);
app.listen(3000, () => {
  console.log("now listening on port 3000");
});
    
  

We created the route and used the express-graphql middleware with a couple of options: an empty schema and the graphiql flag set to true. Activating graphiql allows us to have a nice tool within the browser to talk with the GraphQL server so when we browse http://localhost:3000/graphql we get:


Graphiql Interface

I would like to call your attention to the error message displayed:

    
{
  "errors": [
    {
      "message": "Query root type must be provided."
    }
  ]
}
    
  

This message shows up because of the empty schema we created earlier. So, how does it work? Schemas in GraphQL are the way we define our queries, meaning what types and relations between types can GraphQL resolve. Types are objects that define the structure of the data using the GraphQL schema language, a language-agnostic way to specify the shape of our data.


For example, let's say we have an array of users:


    
const users = [
  { name: "Mike", id: "1" },
  { name: "John", id: "2" }
];
    
  

Then we define a user type using the graphql Node library like this:


    
const UserType = new GraphQLObjectType({
  name: "User",
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString }
  })
});
    
  

What did we do? We created a GraphQLObjectType, gave it a name, and defined a set of fields. Each field has a type from the GraphQL type system. The id is a GraphQLID, which accepts both strings and numbers, and the name is a GraphQLString that defines the field as a string. You can see the rest of the types in the graphql documentation.


Schema definition


Let’s create the schema.js file. Now that we can create types, it is time to define a query for the data of that type in our schema. Let’s first import GraphQL:


    
// schema/schema.js

const graphql = require("graphql");
const {
  GraphQLObjectType,
  GraphQLString,
  GraphQLSchema,
  GraphQLID,
  GraphQLBoolean,
  GraphQLFloat,
  GraphQLNonNull,
  GraphQLList
} = graphql;
    
  

We've just destructured the graphql object to get some types we'll need in a moment. Now let's create a couple of arrays to hold our users and items.

    
// schema/schema.js
...
const users = [
  { name: "Mike", id: "1" },
  { name: "John", id: "2" }
];
const items = [
  {
    id: "e4e39e95-c7bc-4ea0-a750-d38cd493dc4d",
    text: "Milk",
    qty: "5",
    completed: false,
    userId: "1"
  },
  {
    id: "4237305f-baab-4dee-8258-b06231f12668",
    text: "Eggs",
    qty: "18",
    completed: true,
    userId: "1"
  },
  {
    id: "1fb68592-7bf4-4341-8356-335be13751f4",
    text: "Juice",
    qty: "1",
    completed: false,
    userId: "2"
  }
];
    
  

And now let's create the types for that data:


    
// schema/schema.js
...
const UserType = new GraphQLObjectType({
  name: "User",
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString }
  })
});

const ItemType = new GraphQLObjectType({
  name: "Item",
  fields: () => ({
    id: { type: GraphQLID },
    text: { type: GraphQLString },
    qty: { type: GraphQLFloat },
    completed: { type: GraphQLBoolean }
  })
});
    
  

Note that we’ve not used the userId property for the item, since we’ll be using it later when defining the relationships. Now that we have our types, it is time to define our queries and schema.


    
// schema/schema.js
...

const RootQuery = new GraphQLObjectType({
  name: "RootQueryType",
  fields: {
    items: {
      type: new GraphQLList(ItemType),
      resolve(parent, args) {
        return items;
      }
    }
  }
});
 
module.exports = new GraphQLSchema({
  query: RootQuery
});
    
  

We defined a query for our items inside the RootQuery object, the type is an instance of GraphQLList which, as the name suggests, tells GraphQL that it is a list of the shape of ItemType, which we previously defined. Something interesting to note is the resolve function: if we query for items, the resolve function should return a list of items that match the ItemType shape. Finally, we export an instance of the GraphQLSchema with our query. This is the schema that we need to provide to the express-graphql middleware.


    
// index.js
...
-const GraphQLSchema = require("graphql").GraphQLSchema;
+const schema = require("./schema/schema");
...
 graphqlHTTP({
-   schema: new GraphQLSchema({}),
+   schema: schema,
    graphiql: true
  })
...
    
  

Now if we run node index.js and access http://localhost:3000/graphql:


Graphiql Interface without error

Great! There is no error message and in the panel on the right we can see our schema definition. Now it's time to make our queries.


Queries and relationships


A GraphQL query is a specially structured string that we defined to obtain some data from the server. Let's see how it's structured.


    
{
  items{
    id
    text
    completed
  }
}
    
  

So we put the items string inside curly brackets; this string must match the items key we put inside fields on the RootQuery object. Then, we put all the fields from the ItemType object that we want to retrieve inside curly brackets; in this case: id, text and completed. When we press play we get the following:


Results from GraphQL server

Awesome, we have our item list! What if I want to know which user created the item – how can we create a relationship? Let's get back to the ItemType and do that.


    
// schema/schema.js
...
const ItemType = new GraphQLObjectType({
...
    completed: { type: GraphQLBoolean },
    user: {
      type: UserType,
      resolve(parent, args) {
        return users.find(user => user.id === parent.userId);
      }
    }
...
    
  

We've added the user property to the ItemType and said that it’s the UserType we previously defined. Now if a client wants to load the relationship, GraphQL can get the data from the resolve function. If we need to load the user data, it will find a user whose id matches the parent userId value. That parent is an item from the shopping list, so GraphQL gives us access to all of its properties and calls the resolve function for every item on that list. To query the items and the name of the user that created it, we use the following:


    
{
  items{
    id
    text
    completed
    user{
      name
    }
  }
}
    
  

And we get:


GraphQL query results with a loaded relationship

Great! We have the data we were looking for! But wait, what if we want to get all the items from the shopping list of a specific user? We will need to define some arguments and filter the result.


    
// schema/schema.js

...
const RootQuery= new GraphQLObjectType({
...
        type: new GraphQLList(ItemType),
-       resolve(parent, args) {
-           return items;
-       }
+       args: { userId: { type: GraphQLID } },
+       resolve(parent, args) {
+           return items.filter(item => item.userId === args.userId);
+       }
...
    
  

Now GraphQL knows about some arguments that are needed when querying the items, named the userId args. We told GraphQL that this argument is of GraphQLID type, which means we can pass a string or a number as a value. Then, we modified the resolve function to use the args parameter to filter the shopping items. The args parameter is an object with the userId data that we previously defined. So this translates to a query like:


    
{
  items(userId:1){
    id
    text
    completed
    user{
      name
    }
  }
}
    
  

We use parentheses to pass some arguments to the items query, in this case, the previously mentioned userId. Now if we run the query it should show something like:


GraphQL query filtered with arguments

And we get only the items from the user with id equal to 1. But, in the same way, we can define more args and filter them as needed.


Mutations


Now that we have a way for the client to query the shopping list and the user, let’s find a way to update that list. Let’s use mutations to add a new item: a mutation is a convention to signal a possible data-write from a query. We are going to create a new GraphQLObjectType for our mutation type; we set the args as the fields we want to save and the resolve function will handle the saving.


    
const Mutation = new GraphQLObjectType({
  name: "Mutation",
  fields: {
    addItem: {
      type: ItemType,
      args: {
        id: { type: new GraphQLNonNull(GraphQLID) },
        text: { type: new GraphQLNonNull(GraphQLString) },
        qty: { type: new GraphQLNonNull(GraphQLFloat) },
        completed: { type: new GraphQLNonNull(GraphQLBoolean) },
        userId: { type: new GraphQLNonNull(GraphQLID) }
      },
      async resolve(parent, args) {
        const arrLength = items.push(args);
        return items[arrLength - 1];
      }
    }
  }
});
    
  

Then we add the new mutations object to the schema and then query:


    
module.exports = new GraphQLSchema({
  query: RootQuery,
+  mutation: Mutation
});
    
  

Notice that we had to add the mutation keyword before the curly brackets in order to signal GraphQL that what we want to invoke may be written to disk!


    
mutation {
  addItem(
    	id: "ee4e73ea-62d4-11ea-bc55-0242ac130003", 
    	text: "Aples", 
    	qty: 12, 
    	completed: false, 
    	userId: 2
  ) {
    id
    text
    qty
    user {
      name
    }
  }
}
    
  

Now we have a way to query and update our data using GraphQL. As we can see, the mutation returned the created shopping item. We can do the same as with any other GraphQL type and request only certain fields or load the related user.


Executing Mutations

Conclusion


Thank you for reading this article. I hope you have gotten a taste of the power of GraphQL. This layer is completely agnostic to your data storage – you could have a file, an SQL database, a NoSQL database, or even an in-memory object like in this example – you just need to replace the resolve function on the GraphQLObjectType, which can also be async. GraphQL is useful for a variety of reasons, but especially because it loads precisely the data your client needs, which leads to decreases in network requests and smaller payloads. These features are especially attractive to mobile clients, where size and number of requests can have a significant impact on user experiences.


---
At FullStack Labs, we are consistently asked for ways to speed up time-to-market and improve project maintainability. We pride ourselves on our ability to push the capabilities of these cutting-edge libraries. Interested in learning more about speeding up development time on your next form project, or improving an existing codebase with forms? Contact us.

Let’s Talk!

We’d love to learn more about your project. Contact us below for a free consultation with our CEO.
Projects start at $25,000.

FullStack Labs
This field is required
This field is required
Type of project
Reason for contact:
How did you hear about us? This field is required