Unveiling the Power of GraphQL with Apollo: Streamlining Your API Experience

Zuda Pradana Putra
7 min readDec 29, 2023

GraphQL, a powerful query language for APIs, has emerged as a revolutionary approach to fetching and manipulating data in modern web development. Unlike traditional REST APIs, GraphQL allows clients to request only the specific data they need, providing a more flexible and efficient communication channel between clients and servers. Developed by Facebook and open-sourced in 2015, GraphQL has gained widespread adoption for its ability to streamline data retrieval and manipulation processes, offering a versatile solution to meet the diverse needs of applications across various domains.

Interaction process between GraphQL client with GraphQL server and REST API

The figure illustrates how GraphQL allows clients to interact with servers through a single endpoint, while those servers communicate with multiple REST APIs to retrieve or send data.

GraphQL’s efficiency becomes particularly evident in complex projects where traditional REST APIs might struggle to meet the demands of diverse client requirements. One key factor contributing to GraphQL’s effectiveness is its ability to retrieve multiple resources in a single request, eliminating the need for multiple endpoints and reducing the over-fetching of data. Additionally, GraphQL empowers clients to specify the structure of the response, avoiding the issue of under-fetching and ensuring that clients receive precisely the data they expect. This granularity in data retrieval, combined with a single endpoint for queries and mutations, significantly minimizes network latency and enhances the overall performance of applications dealing with intricate data models. As a result, GraphQL stands out as a pragmatic choice for developers navigating the complexities of modern, feature-rich projects, offering both efficiency and performance benefits.

Initializing project

Before diving into the development of a GraphQL-based CRUD application in Node.js, it’s essential to set up the project and install the necessary packages. Start by initializing a new Node.js project using the command:

npm init -y

This command creates a package.json file with default values. Once the project is initialized, proceed to install key dependencies:

npm i mongoose graphql apollo-server

These packages lay the foundation for developing a GraphQL-powered CRUD application with MongoDB. Mongoose will handle database interactions, GraphQL will facilitate efficient querying, and Apollo Server will serve as the middleware to process and respond to GraphQL requests. With these dependencies in place, you’re ready to start building your GraphQL application.

Create simple models User data mongo

const mongoose = require("mongoose");

const UserSchema = mongoose.Schema({
username: String,
email: String,
phoneNumber: String,
address: String,
});

const User = mongoose.model("User-graphql", UserSchema );

module.exports = User;

As we progress in building our GraphQL application, this model will be instrumental in handling CRUD operations related to user data. Whether it’s querying user information or updating user details, our MongoDB model, powered by Mongoose, lays the groundwork for seamless interactions between GraphQL and the underlying database.

Setup Server Mongo and Apollo-Server

const mongoose = require("mongoose");
const { ApolloServer } = require("apollo-server");

const typeDefs = require("./schema");
const resolvers = require("./resolvers");

//connection mongodb
mongoose.connect("mongodb://127.0.0.1:27017/test-user-graphql");

const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`server running at ${url}...`);
});

In the index.js file, we initiate the configuration for our GraphQL application, leveraging the Apollo Server library in conjunction with MongoDB. As per the Apollo Server documentation, two essential arguments are provided during server instantiation: typeDefs and resolvers.

  1. TypeDefs: Defining Data Structure The typeDefs represent the GraphQL schema, defining the structure of available data. This schema outlines a hierarchy of types, each with specified fields that map to the backend data store. It precisely dictates the queries and mutations accessible to clients, acting as a blueprint for data interactions within the GraphQL API.
  2. Resolvers: Handling Data Logic Resolvers, on the other hand, are functions responsible for populating data for individual fields in the schema. Each resolver corresponds to a specific type or field and dictates how the data should be retrieved or manipulated. This separation of concerns allows for a modular and organized approach to handling the underlying logic of data retrieval, ensuring flexibility and maintainability in the GraphQL application.
  3. MongoDB Connection: Establishing Data Store Link Prior to server initiation, we establish a connection to MongoDB using the mongoose.connect method. This links our GraphQL application to a MongoDB database, enabling seamless integration between the defined schema and the persistent data store.
  4. Server Start: Activating GraphQL Server With the Apollo Server instance configured with the schema and resolvers, the server.listen() method launches the GraphQL server. The console then logs the server's URL, marking the successful setup of a GraphQL application with MongoDB connectivity.

This concise overview illustrates the fundamental components and their roles in establishing a GraphQL server with Apollo, emphasizing the importance of schema definition (typeDefs) and data logic handling (resolvers) in conjunction with MongoDB for a robust and efficient API implementation.

Graphql Schema Definition

const { gql } = require("apollo-server");

const typeDefs = gql`
type Users {
_id: ID!
username: String!
email: String!
phoneNumber: String
address: String
}
type Query {
getAllUsers: [Users]!
getUsersById(_id: ID): Users!
}
type Mutation {
createUser(
username: String!
email: String!
phoneNumber: String
address: String
): Users!
updateUser(
_id: ID!
username: String
email: String
phoneNumber: String
address: String
): Users!
deleteUser(_id: ID!): Boolean
}
`;

module.exports = typeDefs;

The Users type represents the structure of a user in the GraphQL schema. Each field is assigned a data type (e.g., String, ID) and may have an exclamation mark (!) denoting non-nullable fields.

The Query type defines operations that retrieve data. In this case, there are two queries:

  • getAllUsers: Returns an array of Users. The exclamation mark indicates that the array itself and its elements cannot be null.
  • getUsersById(_id: ID): Takes an _id as an argument and returns a single Users object.

The Mutation type defines operations that modify data. Three mutations are defined:

  • createUser: Creates a new user with specified properties and returns the created Users object.
  • updateUser: Updates an existing user based on the provided _id and other optional parameters, returning the modified Users object.
  • deleteUser(_id: ID!): Deletes a user based on the given _id and returns a Boolean indicating the success of the deletion.

GraphQL Resolvers: Handling Queries and Mutations

const UsersModel = require("./models/Users");

module.exports = {
Query: {
getAllUsers: async () => await UsersModel.find({}),
getUsersById: async (_, args) => await UsersModel.findById(args._id),
},

Mutation: {
createUser: async (_, args) => {
const user = new UsersModel(args);
await user.save();
return user;
},
updateUser: async (_, args) => {
const user = await UsersModel.findByIdAndUpdate(args._id, args, {
new: true, // Save then return new data
});
return user;
},
deleteUser: async (_, args) => {
const user = await UsersModel.findByIdAndDelete(args._id);
if (user) return true;
return false;
},
},
};

Let’s break down how the queries and mutations work in the context of these resolvers:

  • getAllUsers: Retrieves all users from the database using UsersModel.find({}).
  • getUsersById: Retrieves a specific user based on the provided _id using UsersModel.findById(args._id).
  • createUser: Creates a new user by instantiating a UsersModel with the provided arguments, saving it to the database, and returning the created user.
  • updateUser: Updates an existing user based on the provided _id and other optional parameters using UsersModel.findByIdAndUpdate. The { new: true } option ensures that the updated user is returned.
  • deleteUser: Deletes a user based on the given _id using UsersModel.findByIdAndDelete. It returns true if the user was successfully deleted, otherwise false.

These resolvers act as the bridge between the GraphQL schema and the underlying MongoDB data store, executing the logic for retrieving, creating, updating, and deleting user data as specified in the GraphQL schema’s queries and mutations.

Executing Mutations and Query in GraphQL Playground: A Seamless Experience

To try out the mutation (e.g., adding a new user) and query (e.g., searching for a specific user) functions in Apollo Server, follow these steps:

node index.js

Once the server is running, open a browser and visit http://localhost:4000/apollo-server. You will see the GraphQL Playground user interface.

CreateUser Mutation

In the GraphQL Playground, you are provided with a user-friendly interface that streamlines the process of crafting and executing queries and mutations for your GraphQL API. Let’s take the example of the CreateUser mutation to illustrate how effortless it is to interact with your GraphQL schema. In the GraphQL Playground, you can simply click on the CreateUser mutation and fill in the required and optional fields directly. The use of variables, such as $username, $email, $phoneNumber, and $address, allows for dynamic input. The curly braces within the mutation define what data you want to retrieve as output. After filling in the necessary details, clicking the “Run” button will execute the mutation. The GraphQL Playground seamlessly communicates with your GraphQL server, creating a new user and returning relevant information such as the user’s ID, username, and email.

Following the successful creation of a new user, it’s essential to ensure that the data has been persisted in MongoDB. Let’s perform a check using the following GraphQL query:

Search User by _id

Executing this query verifies that the user’s data, including ID, username, email, phoneNumber, and address, aligns with the expected values, confirming the seamless integration between GraphQL and MongoDB. With all components working harmoniously, GraphQL proves its efficacy in handling diverse data retrieval scenarios.

Conclusion on GraphQL: Unifying Efficiency in Endpoint Management

In conclusion, GraphQL emerges as a powerful solution for projects with extensive endpoint requirements. Its efficiency stems from consolidating multiple endpoint functionalities into a single query, reducing over-fetching of data and enhancing response times. The ability to request precisely the needed data minimizes network load and accelerates application performance. GraphQL’s flexibility and speed make it an invaluable tool, particularly in complex projects where optimizing data retrieval and minimizing network resources are paramount.

--

--