Let’s Build a GraphQL Server with Node.js

Aziz Kale
JavaScript in Plain English
9 min readJan 11, 2021

--

GraphQL is a query language, which Facebook developed. It can be said that it is improved version of REST approach. In this article, i want to talk about how to use it rather than what it is. However, compared to REST, we can say following advantages of GraphQL:

1. In REST queries, you need Endpoints. And as long as these Endpoints elongate, it can be difficult to read. For example:

https://domain.com/api/customers/customerId/products/productId/orders/orderId/

You don’t need such queries at GraphQL.

2. While using the REST queries, many data that you don’t need can come along with the data you call. In this ways servers are overloaded. GraphQL gets you rid of getting data you don’t need.

3. Nested Query means successive queries in REST. It is easier to make nested queries in GraphQL. Because you need only one query for that. GraphQL divides these queries into pieces and gets related data as Promise and re-gathers together and then returns to us. This saves us time.

4. GraphQL offers a UI called Playground, that we can test our APIs on it.

5. Since all fields in TypeDefs (schema-part of GraphQL) must be already specified, there is no question of forgetting by backend developers to add some fields to functions. It is up to API-user to use or not to use these fields.

Let’s take a look at the structure of GraphQL:

GraphQL consists of two main parts:

1. TypeDefs: the part where we specify the API’s schema. In this section, the types of functions and variables are determined.

2. resolvers: the part where functions are defined. CRUD functions are handled in two parts under resolvers in GraphQL; “Read” functions are defined under “Query”, others under “Mutation”.

Let’s see these features in practice with a simple example.

Since GraphQL is a query language, we need a tool to use it. I prefer to use graphql-yoga because we are creating a server with Node.js. However, multiple tool possibilities suitable for each language are available at the link https://graphql.org/code/.

First of all, I create the package.json file in the folder, which I will install my server:

npm init –y

Then I install babel, a JavaScript compiler:

npm i babel-cli babel-preset-es2015

Babel basically provides syntax convenience. It converts codes which are written with ES 2015 and later versions to JavaScript compatible with browsers.

Let’s install graphql-yoga tool now.

npm i graphql-yoga — save

Let’s create the index.js file, in which we write our server codes in the main directory and add the following codes into it:

import { GraphQLServer } from “graphql-yoga”;
const typeDefs = `
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => “Selam.”,
}
};
const server = new GraphQLServer({ typeDefs, resolvers });server.start(() => console.log(“Server is running on localhost:4000”));

I prefer to run index.js by nodemon. nodemon is a node.js package that gets you out stopping and restarting your project every time during the development phase.

npm i –g nodemon

Let’s add the following code to scripts in the package.json file.

“start”: “nodemon index.js — exec babel-node — presets es2015”,

Our server is working in its simplest form right now.

To mention briefly:

  • In the first line, we imported the relevant tool / package
  • We specified the types under TypeDefs
  • We define the function in resolvers
  • Finally, we got instance from the graphql-yoga package we installed, and run the server.

GraphQL runs on port 4000 by default. You can use the following code to run on different ports.

server.start({ port: 4001 }).then(console.log(“server runs on port localhost:4001”));

Let’s run our project now.

npm start

In browser, Playground UI comes we mentioned above.

We can call our defined functions on the left and see the results on the right.

Let’s make some different queries now. For this, let’s create fake data that consists of two arrays. We can add the fake data just above the TypeDefs.

const Users = [
{
id: 1,
username: “john”,
city: “Melbourne”,
},
{
id: 2,
username: “mseven”,
city: “Istanbul”,
},
{
id: 3,
username: “maria”,
city: “Zagreb”,
},
];
const Posts = [
{
id: 1,
title:“Lorem Ipsum is simply dummy text of the printing and typesetting industry.”,
userId: 1,
},
{
id: 2,
title:"Lorem Ipsum je jednostavno probni tekst koji se koristi u tiskarskoj i slovoslagarskoj industriji.”,
userId: 3,
},
{
id: 3,
title:“Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir.”,
userId: 2,
},
{
id: 4,
title:"Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir22222.”,
userId: 1,
},
];

Data, in short, indicates the users in the Users array and the posts that make up the Posts array made by these users.

First, let’s create the schemas of the data under TypeDefs.

const typeDefs = `
type Query {
hello: String
}
type User{
id: ID!
username: String!
city: String
}
type Post{
id: ID!
title: String!
userId: ID!
}`;

Here I want to talk about two features of GraphQL. “ID” type is a special type that given to ‘id’s in GraphQL. The id-parameters that specified in this way can be passed to the function as String or Int. Another feature is the exclamation point. Variables with exclamation points are non-nullable variables.

Now let’s specify the types of functions:

type Query {
hello: String
users: [User!]!
posts: [Post!]!
}

Since these functions will list all users and posts, we write their types in “[]”. This means it will return an array to us. The exclamation mark inside the parentheses indicates that the field is required and the result that returned in the User type cannot be null, and the exclamation point outside of the parenthesis indicates that the returned result in array type cannot be null.

Now let’s define the functions that we formed their schemas in resolvers. Since these functions are “Read” functions, we will define them under Query.

const resolvers = {
Query: {
hello: () => “Selam.”,
users: (parent, args) => Users,
posts: (parent, args) => Posts,
},
};

I will refer to the concepts parent and args later in the article.

When we run the project and make the relevant query on the left side of the Playground, we get the output that appears on the right:

We call the functions that we defined as a query with query keyword in Playground. And we can determine the fields ourselves that need to return. For example, when you remove the city field, the function will still work. However, at least one field must remain.

Now let’s try to get a user by passing its id to query.

First, in the schema we determine the function type, parameters and their types:

type Query {
hello: String
users: [User!]!
posts: [Post!]!
user(id: ID!): User!
}

Then we define the functions in resolvers:

const resolvers = {
Query: {
hello: () => “Selam.”,
users: (parent, args) => Users,
posts: (parent, args) => Posts,
user: (parent, args) => Users.find((user) => String(user.id) === args.id),
},
};

Here I want to talk about the concept args. Args is an object and actually refers the parameters of the defined function. As seen in the user function above, the passed id is represented as “args.id”.

Now when we run the project and make the query that appears on the left, we get the result on the right:

The user with an id of 1 has been got. Since the type of id parameter is ID , it can be passed to function as Int or String.

Nested Query in GraphQL

Let’s make a Nested Query example:

For example, let’s have a scenario in which an author has more than one post. According to our fake data, the user whose id is 1 has already two posts. However, since a post cannot have more than one author, each post will correspond to one user.

Now let’s form the queries that return posts of an author and the author of a post. Again, we start with the schema first. In TypeDefs we add “user” field to Post and “posts” field to User:

type User{
id: ID!
username: String!
city: String
posts: [Post!]
}
type Post{
id: ID!
title: String!
userId: ID!
user: User!
}

But when we look at the data, just as there is no posts field in Users array, there is no user field in Posts Array. We will solve this problem by defining functions in resolvers that match the user’s id and the post’s userId. However, since these fields are not available in the arrays, we cannot define them under Query.

To add user to Post type, we define a separate resolver which is called Post. Likewise, we define a separate resolver which is called User to define a posts field for User:

const resolvers = {
Query: {
hello: () => “Selam.”,
users: (parent, args) => Users,
posts: (parent, args) => Posts,
user: (parent, args) => Users.find((user) => String(user.id) === args.id),
}, Post: {
user: (parent, args) => Users.find((user) => user.id === parent.userId),
}, User: {
posts: (parent, args) => Posts.filter((post) => post.userId === parent.id),
},
};

This is where the parent concept comes into the picture. As the name suggests, in a Nested Query, the parameters of “parent query” are represented by “parent” argument so that we can use them in child query.

For example, let’s read the second function. To get posts of user whose id is 1, it checks Posts array and gets the post whose userId equals to user’s id which is required.

Now let’s make a mutation query. Let’s create a function that adds a new User to Users array. I add the following code to TypeDefs:

type Mutation{
addUser(id:ID!, username:String!, city:String! ): User
}

Let’s define the resolver of this function which we have specified its schema. In resolvers, we write the following codes under Mutation:

Mutation: {
addUser: (_, { id, username, city }) => {
const newUser = {
id: id,
username: username,
city: city,
}; Users.push(newUser);
return newUser;
},
},

Here we see that the first parameter of the function is an underscore. Since we don’t use the parent parameter here, we can prefer this practical syntax. The second parameter is actually an object, that consists of three parameters, which we pass them to the function. (in curly brackets)

When we run the function and run the query on the left, we get the following results:

Conclusion

To sum up, we built up a GraphQL web server in Node.js by using graphql-yoga library. We made examples by explaining the concepts TypeDefs and resolvers, which are two main elements that make up GraphQL.

You can access the whole project from the following link:

https://github.com/azizkale/Web-Server-in-NodeJS-with-GraphQL

For more information, you can visit the GraphQL official site.

UPDATE(17.12.2021):

You can also build a GraphQL Server with another and more up-to-date package: apollo-server

All you need is to add apollo-server to the project:

npm i apollo-server — save

And your package.json file should look like this:

And in the file index.js you should make three changes:

We import ApolloServer from the file apollo-server

instead of

GraphQLServer from graphql-yoga

And we define the server object with the class ApolloServer instead of GraphQLServer.

Finally, we run the server with the method .listen() instead of .start()

After you go to the “http://localhost:4000” address in your browser, a more modern interface of GraphQL-Apollo welcomes us:

--

--