Note: This article assumes that you have a good understanding of NodeJS, Express, concept of Rest APIs, and JS language.
Overview
By definition, GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. Now to explain this better, GraphQL is a technology which allows client applications to decide and request the exact data they need instead of the backend providing a set of API endpoints with predefined response data which is the case with standard REST APIs.
There is a misconception among people just learning this technology that it is enough to install the GraphQL package on the server side to be able to perform specialized requests from the frontend. This is not the case since GraphQL requires a decent amount of coding and infrastructure on the backend to be able to actually use it. We will cover the basics of this process in this article.
GraphQL is available for all major programming languages, and here we will follow the example of a simple NodeJS Article app.
Setup
To integrate GraphQL with the NodeJS Express framework, all we need to do is add the GraphQL middleware to the express pipeline.
app.use('/graphql', graphqlHTTP({
schema: schema,
context: {
db: models
},
graphiql: true,
}));
As we can see, we used the graphqlHTTP middleware (which is part of the express-graphql package). This is one of many available for NodeJS.
It is worth mentioning that there is an Apollo Server package for NodeJS which encapsulates a bunch of GraphQL logic, allows faster development and is actually the most popular package for GraphQL development, but for this article we purposely didn't use it so we can understand the whole process a little better.
The code above does the following:
- creates the /graphql route
- adds graphqlHTTP middleware to handle requests to this route
- adds some configuration to the graphqlHTTP middleware
The "configuration" contains one element which is very important to us, and that is the schema
GraphQL Schema
At the heart of GraphQL's implementation is its schema.
Since GraphQL is a typed system, the schema describes fields and their types and also contains GraphQL "endpoints" and what they should do.
module.exports = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: () => ({
Article,
Articles,
}),
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: () => ({
AddArticle,
UpdateArticle,
}),
}),
});
This is the root of the schema, every root contains the query , mutation or both properties.
The query property contains fields which are basically endpoints (queries), and these queries return data that has been queried, hence query. We can look at them as api GET requests , although it isworth mentioning that every actual /graphql request is a POST request.
The mutation property also contains "endpoints" (mutations) and they modify/add data, hence mutation. We can look at them as api POST/PUT requests.
Lets jump to one of these fields/endpoints, for example the Article query.
const Article = {
type: ArticleType,
args: {
title: {
type: new GraphQLNonNull(GraphQLString),
},
},
resolve (parentValue, args, context) {
return context.db.Article.findOne({
where: {
title: args.title
},
});
}
};
As we can see, Article contains some interesting properties. So lets go through each of them.
The type property. As we already mentioned, GraphQL is a (strongly) typed system, and this property "tells" the Article query what object type to return. In this case it is ArticleType. Let's quickly jump to the ArticleType so we can have a better understanding of the full picture.
const ArticleType = new GraphQLObjectType({
name: 'Article',
fields: () => ({
id: { type: GraphQLInt },
title: { type: GraphQLString },
content: { type: GraphQLString },
})
})
ArticleTypeis a simple type definition, which in this case defines an article object which consists of three properties: integer Id, string title and string content. NOTE: naming doesn't have to end withType, it can be anything else as it is a plain variable name.
The args property, as the name implies, describes what arguments this query accepts. In this case it accepts an article title which is of type string and it cannot be null/not passed.
The resolve property is very important and an interesting one. It is basically a function that is triggered when we call an Article query. Every query and mutation at its root has a resolve function. This function contains 3 arguments:
-
The
parentValueargument represents the response of the previously mentioned resolve function. This is a little more complex functionality which we will cover briefly. Let's imagine that each article has an author, and author would be a separate type, then ourArticleTypetype definition will contain an additionalauthorfield of typeAuthorType. At this point we can decide between two solutions. The first solution would be to eager load the author for an article in theroot resolvefunction. The second solution would be to add aresolvefunction to theauthorfield and lazy load anauthorwhen a request demands it. To do this, we would use theparentValueargument which would probably contain, in this imaginary solution, anauthorId, and we would use that id to fetch theauthor. -
The
argsargument represents arguments passed to this query, in this case it would be the articletitle, which is used, as we can see, to fetch the article by title from the DB. -
The
contextargument represents the context of the current request. We can imagine thecontextobject as a bucket which contains the backend classes or modules we added to it. If we go back to theSetuppart of this article we can see that we provided thecontextwith themodelobject. Themodelobject is a specialized sequelize object for fetching data from the database. We could very well add any other object, for example if our application has some service layer, we could maybe add anArticleServiceobject or anything else we would use in theresolvefunction to resolve the query.
mutations follow the same flow as queries, only difference is, as already mentioned, that they serve for data modifications.
Visual example of GraphQL query
Request:
{
Article(title: "Test article 1") {
title
id
content
}
}
Response:
"Article": {
"title": "Test article 1",
"id": 1,
"content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
}
}
If we, for example, remove the content from the request:
{
Article(title: "Test article 1") {
title
id
}
}
The response would immediately, without any code modifications, adapt:
"Article": {
"title": "Test article 1",
"id": 1
}
}
Rest API vs GraphQL
Following are key differences between Rest API and GraphQL:
| Rest API | GraphQL | |
|---|---|---|
| architecture | server driven | client driven |
| accessed by | endpoints | schema & type system |
| operations | create, read, update, delete | queries & mutations |
| data fetching | fixed data with multiple API calls | specific data with single API call |
| speed | slow-er/potentially multiple network calls | fast/one call with specified data |
| data formats | supports multiple data formats | JSON representation only |
Both GraphQL and REST API have their pros and cons. GraphQL has steep learning curve, and is pretty tricky to set up (in comparison with REST), but once initial "difficulties" have passed, you have yourself awesome easily extendible ecosystem for sending simple query strings for fetching data that you really need. Main "selling" point of GraphQL is its no over-fetching and under-fetching ability. It is a powerful tool for client-side collaboration, but, GraphQL is not "jack of all trades" and is not replacement for good old REST.
For me, personally, reason to go with GraphQL over REST is within microservices architecture and when there is a need for supporting multiple devices, simply because GraphQL simplifies data retrieval. At the end of the day, this is largely subjective, and depends on specific requirements and other factors.
Conclusion
As we can see, adding GraphQL is fairly simple, but as you can imagine, adding it into existing projects is not a walk in the park. Once the setup is done, we have a very flexible and upgradeable functionality which can easily support a number of different devices and client applications.
Source code used for this article is from the following github repo.


