Build a Full Stack App using GraphQL as a Backend API (Part 2)

Yogesh Chavan
Level Up Coding
Published in
7 min readJul 28, 2020

--

Use MongoDB + GraphQL + React + Node.js Stack to build the App.

Photo by Carlos Muza on Unsplash

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.

products query result

GraphQL mutation to add a new product:

mutation {
addProduct(
skuId: "1234",
data: {
name: "Shoes", description: "Nice Shoes",
price: 3900
}) {
skuId
name
description
price
updatedAt
}
}
add product mutation

GraphQL mutation to edit product:

mutation {
editProduct(skuId: "1234", data: {
name: "Shoes",
description: "Best quality shoes"
price: 31000
}) {
skuId
name
description
price
updatedAt
}
}
edit product mutation

GraphQL mutation to delete a product:

mutation {
deleteProduct(skuId: "1234") {
skuId
name
description
price
updatedAt
}
}
delete product mutation

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
}
}
add review mutation

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"
)
}
delete review mutation

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.

--

--