Build a Full Stack App using GraphQL as a Backend API (Part 2)
Use MongoDB + GraphQL + React + Node.js Stack to build the App.
This is part two of the series of building a Full Stack Application using GraphQL API. If you have not checked part one, check that out HERE.
In the last article, we have explored the basics of GraphQL from scratch. In this article, we will create GraphQL APIs for our Full Stack Application.
Let’s get started
Clone the repository code of the previous article from HERE
Switch to the code branch
git checkout graphql_initial_code
Create and switch to the new git branch by executing the following command
git checkout -b part2_full_stack_app
Install the required dependencies
yarn add mongoose@5.9.25 lodash@4.17.19
Open schema.graphql
from server/src
folder and replace its contents with the content from HERE
In this file, we have added two queries first for getting all products and another for getting reviews of a specific product.
For mutation, we have added add
, edit
and delete
product and for reviews add
and delete
review queries.
If you noticed mutation, we have mutation like this
addProduct(skuId: ID!, data: ProductInput): Product!
and we have ProductInput
input type declared as
input ProductInput {
name: String!
description: String!
price: Float!
}
So instead of addProduct(skuId: ID!, name: String!, description: String!, price: Float!): Product!
, we have moved the additional arguments to separate input type ProductInput
which a pretty common practice to keep the queries simple and easy to manage.
So while accessing the arguments in mutation instead of args.name
or args.description
we need to use args.data.name
or args.data.description
because we have named the argument as data
in the arguments list (data: ProductInput
).
That’s the only new change here. All other schema code will look familiar if you have read part one of this series.
Now, create a new db
folder inside server
folder and add connection.js
file inside it with the following content
const mongoose = require('mongoose');mongoose.connect('mongodb://127.0.0.1:27017/ecom_products', {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false
});
Here, we have added MongoDB connection details to connect to the local MongoDB database. Check out this article to install the MongoDB database locally if you don’t have it installed.
Create a new model
folder inside server
folder and add Product.js
and Review.js
files inside it.
Inside Product.js
add the following content
const mongoose = require('mongoose');const ProductSchema = mongoose.Schema(
{
skuId: {
type: String,
required: true,
unique: true
},
name: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
}
},
{
timestamps: true
}
);const Product = mongoose.model('Product', ProductSchema);module.exports = Product;
In this file, we have defined MongoDB database schema with skuId
, name
, description
, price
fields of the Product
collection. skuId
is a unique product Id used in the eCommerce applications.
We also added timestamps: true
property to the model so each document will have createdAt
and updatedAt
timestamps automatically added.
Inside Review.js
file, add the following content
const mongoose = require('mongoose');const ReviewSchema = mongoose.Schema(
{
title: {
type: String,
required: true
},
comment: {
type: String,
required: true
},
product: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Product'
}
},
{
timestamps: true
}
);const Review = mongoose.model('Review', ReviewSchema);module.exports = Review;
In this file, we have added title
, comment
and product
fields for the Review
collection. product
is a foreign key field referencing the _id
field from the Product
model.
Open src/index.js
and replace it with the following content
const { GraphQLServer } = require('graphql-yoga');
const Query = require('./resolvers/Query');
const Mutation = require('./resolvers/Mutation');
require('../db/connection');const server = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers: {
Query,
Mutation
}
});const options = {
port: 4000,
endpoint: '/graphql'
};server.start(options, ({ port }) =>
console.log(`server started on port ${port}.`)
);
Rename the users.js
file from utils
folder to functions.js
and replace it with the following content
const getMessage = (message) => {
return message.substring(message.indexOf(':') + 1);
};module.exports = { getMessage };
Now, open Query.js
file from resolvers
folder and replace it with the following content
const _ = require('lodash');
const Product = require('../../model/Product');
const Review = require('../../model/Review');
const { getMessage } = require('../utils/functions');const Query = {
async products() {
try {
let products = await Product.find({});
products = _.orderBy(products, ['updatedAt'], ['desc']);
return products;
} catch (error) {
throw new Error('Error while getting list of products. Try again later.');
}
},
async reviews(parent, args, ctx, info) {
try {
const { skuId } = args;
const product = await Product.findOne({ skuId });
if (!product) {
throw new Error(`Message:Product with skuId ${skuId} does not exist.`);
}
let reviews = await Review.find({ product: product._id });
reviews = _.orderBy(reviews, ['updatedAt'], ['desc']); return reviews;
} catch (error) {
const message = error.message;
if (message.startsWith('Message')) {
throw new Error(getMessage(message));
} else {
throw new Error('Error while getting reviews. Try again later.');
}
}
}
};module.exports = Query;
In this file, we have added a resolver products
function to get all the products added in MongoDB database using the find
method on Product
model and then we’re using lodash’s orderBy
method to get the products sorted by updated date in descending order.
products = _.orderBy(products, ['updatedAt'], ['desc']);
Then we have added a reviews
resolver function where we’re taking skuId
and get all the reviews related to that skuId(product)
.
Inside the function, first we check If there is a product with provided skuId
const product = await Product.findOne({ skuId });
If there is no such product, we throw an error. If the product exists, we take all the reviews for that product
let reviews = await Review.find({ product: product._id });
Now, open Mutation.js
file from resolvers
folder and replace it with the content from HERE
In this file, inside the addProduct
resolver function, we’re first checking if there is already a product in the database with the same skuId
. If no such product exists then we’re adding that product to the database using product.save
method.
Inside editProduct
function, we’re first checking if the product to edit exists or not. If it exists then we’re adding the fields to update in the fields
object and calling findOneAndUpdate
method to update that product.
Note, we’re passing { new: true }
as the last argument to findOneAndUpdate
function to get the updated product back so we can return that back to the client.
If we don’t pass the { new: true }
argument, then we will get the old product details back which was before the update.
Inside the deleteProduct
function, we’re deleting the product by skuId
. If the product is deleted, there is no point in keeping its reviews so we’re also deleting all its reviews using deleteMany
method.
Inside the addReview
function, we’re first getting the product for the provided skuId
and then adding the review in the Review
collection along with the product id (_id)
which is a foreign key in Review
model.
Inside the deleteReview
function, we’re deleting the review by its id
. If the deletion is successful, then we’re returning true
.
That’s the reason, we have added Boolean
as the return type of deleteReview
mutation in schema.graphql
file.
deleteReview(reviewId: ID!): Boolean!
Remove the Subscription.js
file from resolvers
folder as we will not be using Subscription
in this app.
Now, we can test all queries and mutations.
Start the server by running yarn start
command from server
folder and navigate to http://localhost:4000/
Also, make sure you have started the MongoDB server by executing the ./mongod --dbpath=<path_to_mongodb-data_folder>
command as explained in this article.
GraphQL query for getting all products:
query {
products {
skuId
name
description
price
updatedAt
}
}
Initially, we don’t have any products so we’re getting an empty array.
GraphQL mutation to add a new product:
mutation {
addProduct(
skuId: "1234",
data: {
name: "Shoes", description: "Nice Shoes",
price: 3900
}) {
skuId
name
description
price
updatedAt
}
}
GraphQL mutation to edit product:
mutation {
editProduct(skuId: "1234", data: {
name: "Shoes",
description: "Best quality shoes"
price: 31000
}) {
skuId
name
description
price
updatedAt
}
}
GraphQL mutation to delete a product:
mutation {
deleteProduct(skuId: "1234") {
skuId
name
description
price
updatedAt
}
}
As you can see, once the product is deleted, trying to delete it again shows us an error that Product with skuId 1234 does not exist.
so our validations are also working fine.
GraphQL mutation for adding a review:
mutation {
addReview(skuId: "1234", data:{
title: "First Review",
comment: "Description for first review"
}) {
_id
title
comment
updatedAt
}
}
GraphQL mutation for deleting a review:
In the below mutation, use the _id
field value coming from addReview
query above for reviewId
field value.
mutation {
deleteReview(
reviewId: "5f1eb33ca6ef0c0ba026370e"
)
}
That’s it for this article. In the next article, we will build a React App which will use these GraphQL APIs.
You can find complete source code for this article in this branch
Check out the next part in this series HERE
That’s it for today. I hope you learned something new.
Don’t forget to subscribe to get my weekly newsletter with amazing tips, tricks, and articles directly in your inbox here.