GraphQL is a powerful query language that allows clients to request and receive only the data they need from an API. In order to use GraphQL in an Express.js application, you’ll need to install the required packages and set up a GraphQL server.
First, you’ll need to install the following packages using npm or yarn:
- `express`: A web framework for Node.js
- `express-graphql`: A middleware that creates a GraphQL HTTP server with Express.js
- `graphql`: A library for building GraphQL schemas and executing queries
GraphQL has become a game-changer in the dynamic landscape of web and mobile development, providing a more efficient and flexible way to interact with APIs.
This article explores the key advantages of GraphQL, with a focus on its synergy with React, a popular JavaScript library for building user interfaces.
Understanding the foundations of GraphQL:
- Efficient Data Retrieval:
Traditional REST APIs tend to give clients a predetermined set of data, which can result in either over- or under-fetching of information. This process is transformed by GraphQL, which empowers clients to specify the precise data they require. API calls can be faster and more efficient due to this capability, which reduces the number of round trips between the client and server. - Seamless API Evolution:
Modifying the underlying data schema without disrupting existing queries is possible with the schema-based approach of GraphQL. This feature promotes better versioning of APIs, enabling smooth evolution over time. Developers can introduce changes iteratively, ensuring compatibility with existing clients. - Another advantage of GraphQL is that it enables easier collaboration between front-end and back-end developers. With GraphQL, front-end developers can define the data requirements for their applications in a simple and concise way, while back-end developers can focus on providing the data in the most efficient way possible.
Limitation of REST API
One limitation of using REST API for retrieving data through multiple separate requests is the issue of over-fetching or under-fetching of data. Over-fetching occurs when the API returns more data than is necessary, resulting in bandwidth usage and slower response times. In the case of under-fetching, the API doesn’t provide enough data in one request, which leads to additional requests to obtain the necessary information. Both scenarios have the potential to affect the efficiency and performance of your application.
Let’s consider a case to demonstrate these limitations:
Imagine that you are developing an e-commerce application that utilizes a REST API to obtain information about products and their categories. Products and categories have separate endpoints provided by the API. To display a list of products and their categories, it may be necessary to make multiple requests to fetch the necessary data.
Example REST API endpoints:
- Get all products:
/api/products
- Get all categories:
/api/categories
Now, let’s say you want to display a list of products with their categories. You might follow these steps:
- Fetch all products using
/api/products
. - For each product, fetch its category using
/api/categories/{categoryId}
.
We would end up being a lot of very separate request just to get this data.
In GraphQL, you have the flexibility to define a query that fetches exactly the data you need, including related entities like categories. Let’s create an example GraphQL query that fetches products along with their categories, and imagine an API endpoint like /graphql
:
query {
products {
id
name
price
category {
id
name
}
}
}
How Server handle the GraphQL request.
Express.js is a web framework that is compatible with Node.js and can be utilized to create web applications. It is not a GraphQL server itself, but it can be used to create a GraphQL server by using a GraphQL library. By using Express.js, HTTP requests can be handled and routed to the GraphQL server.
In addition to routing GraphQL queries, Express.js can also be used to handle other types of HTTP requests, such as GET, POST, PUT, and DELETE. An Express.js server is capable of creating both a RESTful API and a GraphQL API.
Express.js will examine the incoming requests and determine if they are intended for GraphQL processing. If the request is for GraphQL, it will be handed over to GraphQL, which will then be responsible for handling the request. After processing, GraphQL will send the response back to Express, which, in turn, will send the response back to the user.
Middleware are tiny functions meant to intercept or modify request as they come through an express server. So when we register express GraphQL with our application, it was registered as a middleware.
Once you have the packages installed, you can create a GraphQL schema and resolver functions to handle your queries. You can define your schema using the GraphQL schema language, which is a type system for defining the capabilities of an API.
GraphQL Schema
We have to very specifically inform GraphQL about how the data in our application is arranged and how to be accessed. All of this information is encapsulated within a schema file. The schema file serves as the fundamental cornerstone of every GraphQL application. It is responsible for conveying to GraphQL the types of data present in our system and detailing how the relationships between different data entities are structured. Essentially, the schema file is pivotal, outlining the blueprint that GraphQL relies upon to navigate and comprehend the intricacies of our data model.
The crucial aspect to bear in mind regarding your schema file is that it encapsulates all the essential information needed to convey to GraphQL the precise structure of your application’s data. This encompasses vital details such as the properties associated with each object and the precise nature of relationships between different objects.
Essentially, the schema file serves as a comprehensive guide, providing GraphQL with the necessary knowledge to navigate and understand the intricacies of your application’s data model. It acts as a central repository of information, ensuring that GraphQL is informed about the properties of each object and the intricate connections that exist among them.
Writing a GraphQL Schema
We need to provide a crucial element of data called the root query. A root query serves as a gateway, enabling us to navigate our data graph effectively. When we instruct GraphQL, for instance, to fetch a user with an ID of 7, this instruction is channeled through the root query. The root query incorporates logic that directs GraphQL to the specific part of the data graph where the requested information resides.
In essence, we can conceptualize the root query as an entry point into our application or, more precisely, an entry point into our data. The primary role of the root query is to facilitate GraphQL in pinpointing and reaching a highly specific node within the comprehensive data graph.
It’s essential to grasp that the purpose of the root query is to empower GraphQL to seamlessly traverse the graph, enabling it to precisely land on the node containing the requested data.
The resolve
function plays a pivotal role in interacting with our database or data store to retrieve the actual data we are seeking. It serves as the mechanism through which we reach into our data source and extract the relevant information.
The first argument of the resolve
function is often considered somewhat notorious for being seldom used. The true focal point lies in the second argument, commonly referred to as args
. This object captures any arguments passed into the original query, such as an ID for the user we intend to retrieve. If, for instance, the argument contains the user’s ID, that ID will be accessible within the args
object as the second argument of the resolve
function.
In summary, the crux of the root query resides in its resolve
function, which is responsible for fetching and returning actual data from our database, data store, or whichever repository houses our application’s data.
GraphQL schema takes in a root query and return a GraphQL schema instance
http://localhost:4000/graphql?query=
{
user(id: "23") {
id
firstName
age
}
}
. In GraphQL, the resolve
function is typically associated with resolving the value for a specific field in a GraphQL schema. The resolve
function is responsible for fetching the data for that field. When we return a promise from the resolve
function in GraphQL within Express.js, it allows us to handle asynchronous operations seamlessly. The resolve
function serves as our playground for fetching data for a specific field in any desired manner. This is particularly useful for tasks such as database queries or API calls.
This clarifies the role of the resolve
function and its connection to asynchronous operations in the context of GraphQL and Express.js.
We aim to instruct GraphQL on how to traverse from a user to a company. In our GraphQL setup, we have a model and a type. The userType
in our case includes a field called company
, where the user model represents the actual data. It contains only the company’s ID in our scenario. The type is what we define inside our schema file.
Example of GraphQL in Expressjs
The model, which holds the actual data, has a field similar to the type, except the model has the ID of the product, while the type has a order field with a different type. To instruct GraphQL on how to populate this course property, we use the resolve
function. This function helps GraphQL understand how to fetch the necessary data to populate the course field. Let add folder schema in express root folder and add new file called schema.js insidet it.
const graphql = require("graphql");
const axios = require("axios");
const graphql = require("graphql");
const axios = require("axios");
const { GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLSchema } = graphql;
// CourseType representing a course
const CourseType = new GraphQLObjectType({
name: "Course",
fields: {
id: { type: GraphQLString },
name: { type: GraphQLString },
description: { type: GraphQLString },
},
});
// StudentType representing a student
const StudentType = new GraphQLObjectType({
name: "Student",
fields: {
id: { type: GraphQLString },
firstName: { type: GraphQLString },
age: { type: GraphQLInt },
course: {
type: CourseType,
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/courses/${parentValue.courseId}`)
.then((resp) => resp.data);
},
},
},
});
const RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
// Query to get a student by ID
student: {
type: StudentType,
args: { id: { type: GraphQLString } },
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/students/${args.id}`)
.then((resp) => resp.data);
},
},
},
});
module.exports = new GraphQLSchema({
query: RootQuery,
});
Express add ability to other field to access directly from RootQuery
We cannot directly access or fetch the course from the RootQuery. We can directly call orders through the RootQuery, but not products. To add the ability to fetch products directly from the RootQuery
, you can simply add another field for product
within the RootQuery
. Here’s an example:
...
const RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
// Query to get a student by ID
student: {
type: StudentType,
args: { id: { type: GraphQLString } },
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/students/${args.id}`)
.then((resp) => resp.data);
},
},
// Query to get a course by ID
course: {
type: CourseType,
args: { id: { type: GraphQLString } },
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/courses/${args.id}`)
.then((resp) => resp.data);
},
},
},
});
...
In this example, I added a new field named course to the RootQuery
, which allows you to fetch a course directly by its ID. You can now use the GraphQL query to get a product like this:
query {
course(id: "1") {
courseId
courseName
description
}
}
Currently we can access Course data from Student but not possible to access Student data from Course, we want to add feature to retrieve data from vice versa like from Student to Course and Course to Student.
As single Course can have many different Student, currently we can get single course from student, we want to have get multiple Student from a single Course data.
Note we are using JSON server we can access all Student from single course using course id.
http://localhost:3000/courses/1/students
We need to teach Course type in our schema file to get or go from a course over to lists of students. As we have many students associated with single course and we have to tell GraphQL that multiple user are associated with single a company. To represent that a Course
can have multiple students
, you can use GraphQLList
to wrap the StudentType
within the CourseType
. Here’s the corrected code:
...
const CourseType = new GraphQLObjectType({
name: "Course",
fields: {
id: { type: GraphQLString },
name: { type: GraphQLString },
description: { type: GraphQLString },
students: {
type: new GraphQLList(StudentType),
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/courses/${parentValue.id}/students`)
.then((resp) => resp.data);
},
},
},
});
Along with student field, we also need resolve function with parentValue and args, where parentValue had reference to the current Course, the parent value is the instance of the Course that we are currently working with.
The above code will generate error, ReferenceError: Cannot access ‘StudentType’ before initialization, this is not GraphQL issues, it is Javascript clousure issues, we need to add arrow function that will return object that has field and resolve function. This will define the function but doest not get executed until after this entire file will get executed. Then the GraphQL internally will execute this function and resolve the studentType, as the StudentType is now inside of the the closure scope of this clousure function or anonymous function.
const CourseType = new GraphQLObjectType({
name: "Course",
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString },
description: { type: GraphQLString },
students: {
type: new GraphQLList(StudentType),
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/courses/${parentValue.id}/students`)
.then((resp) => resp.data);
},
},
}),
});
Now in browser we can access http://localhost:4000/graphql?query=
{
course(id: "1") {
id
courseName
description
students {
firstName
age
}
}
}
We can also access nested data, accessing course to student and then from student to course as below.
{
course(id: "2") {
id
courseName
description
students {
firstName
age
course {
courseName
}
}
}
}
}
Query Fragment
The query fragment is essentially a list of various properties we aim to access. Let’s first define the fragment, and then we’ll provide an example.
The purpose of these fragments is to minimize the need for copying and pasting properties repeatedly within the query.
{
course(id: "2") {
id
courseName
}
}
Ability to name the query from above to below by adding query with the name
query findCourse {
course(id: "2") {
id
courseName
}
}
Naming the query is useful when working on the frontend, especially when there are multiple queries running concurrently. This allows for the possibility of reusing these queries in different locations within your codebase.
The running the code below in browser will generate error, because GraphQL might be confused their are two course inside the code that we run in browser http://localhost:4000/graphql?query=
{
course(id: "2") {
id
courseName
}
course(id: "2") {
id
courseName
}
}
Now we can fixed this issues by defining the fragment
{
javascript: course(id: "2") {
...courseDetails
}
python: course(id: "2") {
...courseDetails
}
}
fragment courseDetails on Course {
id
courseName
}