This is a NodeJS web-store api appliaction that provides a variety of database Search, Sort and Filter options for web-store users.
The setup used here allows the Frontend to only send HTTP calls to request validated data from the backend.
Postman is used to test the endpoints, no frontend is available for this project.
To try the backend application, please visit the following URL to see all products in the database:
It is recommended to install a JSON Web Formatter such as json-Formatter to see the json data correctly.
NOTE:
To fully test the built features and utilise this backend-app, it is recommneded to use Postman. Point to the following REST API endpoint:
See 'Valid Query Params' section below.
Kindly allow for 30 sec for database Rehydration and loading.
Please make sure you have Node.js
installed on your machine and Git
version
control system to run project locally.
To clone this repository to your local machine, cd
to your desired directory
(e.g cd Desktop
).
Then run the following git clone command. This will create a folder called
Store-api
on your specified directory.
git clone https://github.com/RhonnieAl/Store-api
Run npm install
to install node dependencies
Run npm start
or node app.js
to kickstart server.
In order to run the project, create a .env file, set your "MONGO_URI" with a correct "connection string" to your MongoDB.
Populate the Database with the provided initial data by running :
node populate.js
In order to avoid port collisions, port 3000 is used, feel free to change it.
Great :) Happy testing!
/api/v1/products
- Run all query params on this endpoint
name
- Search products by a specific name (search part of the name)
company
- Search products by company brand (Ikea, JYSK, Isku, Masku)
featured
- Search products on featured ('true') or not featured ('false)
sort
- Determin which property the returned products should be sorted by
(price, name, rating, date, company, featured)
fields
- Select only the product properties you wish to view (price, name,
rating, date, company, featured)
limit
- Determin how many products you want to retrieve
page
- Select the page number you wish to view
numericFilters
- Search and Filter catalogue by "price" and "rating"
Errors are caught globally using middleware error-handler.js
with the help of
library
express-async-errors for
catching Async Errors. The library throws any async error without having to pass
it to the next middleware as native Express does.
Instead of writing an Async-wrapper function with try-catch and next(), we throw
the error with the help of express-async-errors
to our custom error handler in
error-handler.js
and there do anything, for example show it on the console or
return a message on the browser.
MongoDB was auto populated with initial data using a Script in the file
populate.js
.
const start = async () => {
try {
await connectDB(process.env.MONGO_URI);
// Delete any data that is currently in DB (optional)
await Product.deleteMany();
// Create and populate DB with initial data from jsonProducts.js
await Product.create(jsonProducts); // Where the magic happens
console.log("Sucess!!");
// Once done, Close connection.
process.exit(0); // Passing 0 means success
} catch (error) {
console.log(error);
process.exit(1); // Passing 1 means error
}
};
start();
In Mongoose V5 an empty array is returned for queries that do not match the accepted Schema. To account for this, destructured needed queries
controllers
>> products.js
const { featured, company, name } = req.query;
and passed an empty object into .find()
so as not to return epmty array to the
user.
Mongoose V6 takes care of this nateively by ignoreing erroneous queries.
To allow users to serach part of name string instead of typing full product name, MongoDB Regex query operators were used.
// regex: case incensitive and allows partial name input
{ $regex: name, $options: "i" }
{ $regex: company, $options: "i" }
This functionality enables the user to sort data in by any order according name, date, price, featured, company and rating.
By default MongoDB documents in a collection are sorted by date created.
To apply the .sort()
functionality, the method is cahined as shown on
Mongoose Docs.
As a rule .sort()
method is applied on an Object query and not the resolved
result of a query i.e
-
Using a method on the Model with
await
returns the resolved result of a query. -
Using a method on the Model without
await
returns an object (a Query object), not the result of a query.
const { item, sort } = req.query;
result = Product.find(item);
if (sort) {
sortList = sort.split(",").join(" ");
result = result.sort(sortList);
} else {
result = result.sort("createdAt");
}
const products = await result;
res.status(200).json({ products });
This functionality enables a user to select certain fields such as name and price. Returning only data with name and price.
According to
Mongoose Docs,
the method is .select()
Similarly to sort functionality:
// Select
if (fields) {
fieldList = fields.split(",").join(" ");
result = result.select(fieldList);
}
const products = await result;
This functionality allows the user to request a specified certian page if the number of items returned are numerous.
The user also has the ability to determin how many items a single page should have.
To achieve this the following methods were used: .limit()
: determins how many
results are returned
.select()
: determins how many returned items are cut off. if you select(5) it
will return from 6 and over.
.skip()
: specifies the number of documents to skip in the filtered collection
according to
Mongoose Skip docs
// Extract user-given page number or default to 1
const page = Number(req.query.page) || 1;
// Extract user-given num of items requested or default to 10
const limit = Number(req.query.limit) || 10;
// set number of documents in collection to be skipped
const skip = (page - 1) * limit;
// If skip is zero page num begins from page 1 and returns items by limit given
result = result.skip(skip).limit(limit);
const products = await result;
This functionality allows the user to search and filter catalogue based on
price
or rating
or both.
According to
MongoDB docs,
regex such as gt
and gte
are the only accepted comparison operators, instead
of basic comparison operators (eg: <, >, =<, >=).
if (numericFilters) {
// Object to map all user input operators to regex ones
const operatorMap = {
">": "$gt",
">=": "$gte",
"=": "$eq",
"<": "$lt",
"<=": "$lte",
};
// Find all occurences of user input operators
const regEx = /\b(<|>|>=|=|<|<=)\b/g;
// Replace all occurences in "numericFilters" with regex ones
// "filters" is string with swapped operators
let filters = numericFilters.replace(
regEx,
(match) => `-${operatorMap[match]}-`
);
const options = ["price", "rating"];
// Split up the numeric queries passed by the user
filters = filters.split(",").forEach((queryItem) => {
// Destructure and name
const [field, operator, value] = queryItem.split("-");
// Restricing DB fields this fuction applies to
if (options.includes(field)) {
queryObject[field] = { [operator]: Number(value) };
}
});
}
Hobby Project maintined by Rhonnie Allan