Fetching data from MongoDB and displaying it in React App - node.js

I am working on creating a web app of an interactive map of campus for the school I'm attending. The web-app will allow students to click on a specific building on campus, which will bring up a map of the classrooms in that building. From there, a user can then click on a classroom which will then display a list of all classes in that room for each day of the school week. I have created my own database of all the classes offered at my school with attributes such as name, section, building, roomnum, and coursenum. I have done enough of the backend where I can connect to my database using Insonmia/Postman where I am able to filter classes based on building, room number and course section (Accounting, Biology etc.) The problem that I am running into is actually displaying the data from my database on the frontend part of my application.
Here is the backend section of my application so far...
server.js
import express from "express"
import cors from "cors"
import classes from "./api/classes.route.js"
const app = express()
app.use(cors())
app.use(express.json())
app.use("/api/v1/classes", classes)
app.use("*", (req, res) => res.status(404).json({ error: "not found"}))
export default app
index.js
import app from "./server.js"
import mongodb from "mongodb"
import dotenv from "dotenv"
import ClassesDAO from "./dao/ClassesDAO.js"
dotenv.config()
const MongoClient = mongodb.MongoClient
const port = process.env.PORT || 8000
MongoClient.connect(
process.env.UJCLASSES_DB_URI,
{
maxPoolSize: 50,
wtimeoutMS: 2500,
useNewUrlParser: true }
)
.catch(err => {
console.error(err.stack)
process.exit(1)
})
.then(async client => {
await ClassesDAO.injectDB(client)
app.listen(port, () => {
console.log(`listening on port ${port}`)
})
})
classesDAO.js
import mongodb from "mongodb"
const ObjectId = mongodb.ObjectID
let classes
export default class ClassesDAO {
static async injectDB(conn) {
if (classes) {
return
}
try {
classes = await conn.db(process.env.UJCLASSES_NS).collection("Classes")
} catch (e) {
console.error(
`Unable to establish a collection handle in ClassesDAO: ${e}`,
)
}
}
static async getClasses({
filters = null,
page = 0,
classesPerPage = 20,
} = {}) {
let query
if (filters) {
if ("name" in filters) {
query = { $text: { $search: filters["name"] } }
} else if ("section" in filters) {
query = { "section": { $eq: filters["section"] } }
} else if ("course" in filters) {
query = { "course": { $eq: filters["course"] } }
} else if ("room" in filters) {
query = {"room": { $eq: filters["room"] } }
}
}
let cursor
try {
cursor = await classes
.find(query)
} catch (e) {
console.error(`Unable to issue find command, ${e}`)
return { classesList: [], totalNumClasses: 0 }
}
const displayCursor = cursor.limit(classesPerPage).skip(classesPerPage * page)
try {
const classesList = await displayCursor.toArray()
const totalNumClasses = await classes.countDocuments(query)
return { classesList, totalNumClasses }
} catch (e) {
console.error(
`Unable to convert cursor to array or problem counting documents, ${e}`,
)
return { classesList: [], totalNumClasses: 0 }
}
}
static async getSections() {
let sections = []
try {
sections = await classes.distinct("section")
return sections
} catch (e) {
console.error(`Unable to get sections, ${e}`)
return sections
}
}
static async getBuildings() {
let buildings = []
try {
buildings = await classes.distinct("building")
return buildings
} catch (e) {
console.error('Unable to get buildings, ${e}')
return buildings
}
}
static async getRooms() {
let rooms = []
try {
rooms = await classes.distinct("room")
return rooms
} catch (e) {
console.error('Unable to get rooms, ${e}')
return rooms
}
}
}
classes.controller.js
import ClassesDAO from "../dao/ClassesDAO.js"
export default class ClassesController {
static async apiGetClasses(req, res, next) {
const classesPerPage = req.query.classesPerPage ? parseInt(req.query.classesPerPage, 10) : 20
const page = req.query.page ? parseInt(req.query.page, 10) : 0
let filters = {}
if (req.query.section) {
filters.section = req.query.section
} else if (req.query.course) {
filters.course = req.query.course
} else if (req.query.name) {
filters.name = req.query.name
} else if (req.query.building) {
filters.name = req.query.building
} else if (req.query.room) {
filters.room = req.query.room
}
const { classesList, totalNumClasses } = await ClassesDAO.getClasses({
filters,
page,
classesPerPage,
})
let response = {
classes: classesList,
page: page,
filters: filters,
entries_per_page: classesPerPage,
total_results: totalNumClasses,
}
res.json(response)
}
static async apiGetClassSections(req, res, next) {
try {
let section = await ClassesDAO.getSections()
res.json(section)
} catch (e) {
console.log(`api, ${e}`)
res.status(500).json({ error: e })
}
}
static async apiGetClassBuildings(req, res, next) {
try {
let building = await ClassesDAO.getBuildings()
res.json(building)
} catch (e) {
console.log('api, ${e}')
res.status(500).json({ error: e })
}
}
static async apiGetClassRooms(req, res, next) {
try{
let room = await ClassesDAO.getRooms()
res.json(room)
} catch (e) {
console.log('api, ${e}')
res.status(500).json({ error: e })
}
}
}
classes.route.js
import express from "express"
import ClassesCtrl from "./classes.controller.js"
const router = express.Router()
router.route("/").get(ClassesCtrl.apiGetClasses)
router.route("/sections").get(ClassesCtrl.apiGetClassSections)
router.route("/buildings").get(ClassesCtrl.apiGetClassBuildings)
router.route("/rooms").get(ClassesCtrl.apiGetClassRooms)
export default router
I understand that this is not a platform that spits out exact answers, however that is not what I'm looking for. I have been stuck on this problem for over a week and have noticed that my project seems to be different than others. For my project, all I need to do is fetch data from an already completed database and display it. I do not need update, delete or insert functionality.
If anyone could point me in the right direction, or link any docs that could help me out I would be very grateful.

Related

How to dynamically delete MongoDB entry using API route

I would just like to simply delete a record from a dynamically displayed list. I've tried every permutation of the backend code and ai just can't get it to work.
The backend is called like this:
async function deletePost() {
setLoading(true)
try {
await axios.delete(`/api/delete/${id}`)
alert("Post deleted")
}
catch (err) {
// notify user that something went wrong
console.log(err)
}
finally {
setLoading(false)
}
setLoading(false)
}
And /api/delete/${id} looks like this:
import { connectToDatabase } from "util/mongodb"
export default async (req, res) => {
const { id } = req.query;
console.log(id)
try {
const { db } = await connectToDatabase()
await db.collection("users").deleteOne({'_id': `ObjectId("${id}")`})
res.sendStatus(200).send({ done: true })
}
catch (error) {
return res.json({ error })
}
}
The console log shows the correct post id, the alert in the 'try' frontend code displays, but the dam post just wont delete. Can anyone offer any advice please? I have tried ".deleteOne({'_id': id})" but that does nothing either.
I believe you are searching for a string, instead of the ObjectId, so no documents match.
You can fix it by converting to ObjectId and then using the value converted, .deleteOne.
var ObjectId = require('mongodb').ObjectId;
const { id } = req.query;
const convertedObjectId = new ObjectId(id);
db.collection("users").deleteOne({_id: convertedObjectId })
Actual example from documentation:
try {
db.orders.deleteOne( { "_id" : ObjectId("563237a41a4d68582c2509da") } );
} catch (e) {
print(e);
}
Reference: MongoDB Documentation - Delete One.
Fixed problem:
app.delete("/cars/:id", async (req, res) => {
const carsId = req.params.id;
const query = { _id: ObjectId(carsId) };
const result = await carCollection.deleteOne(query);
res.send(result);
});

Why is my graphql apollo resolver not being called?

I'm pretty new to graphql (and nodejs as well). I'm following a Udemy course on Apollo and mongo which has been going well mostly. However I can't get one of the resolvers to be called. Another resolver is working fine, and they appear to use the same layout. Also, the context is being called before the resolver that is not being called, so I know it's at least getting that far.
Here is the root server.js with the working context:
const resolvers = require('./resolvers');
...
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
await verifyUser(req);
console.log("=== context ran, user email : ", req.email) ;
return {
email: req.email,
loggedInUserId: req.loggedInUserId
}
}
});
resolvers are modularized, and combined in a /resolvers/index.js, here:
const { GraphQLDateTime } = require('graphql-iso-date')
const userResolver = require('./user');
const taskResolver = require('./task');
const customDateScalarResolver = {
Date: GraphQLDateTime
}
module.exports = [
userResolver,
taskResolver,
customDateScalarResolver
]
and here is the tasks resolver, located at /resolvers/task.js, which is the one not being called:
const uuid = require('uuid')
const { combineResolvers } = require('graphql-resolvers');
const { users, tasks } = require('../constants');
const Task = require('../database/models/task');
const User = require('../database/models/user');
const { isAuthenticated, isTaskOwner } = require('./middleware');
module.exports = {
Query: {
tasks: async ( _, __, { loggedInUserId }) => {
console.log("tasks query, loggedInUserId : ", loggedInUserId);
try {
const tasks = await Task.find( { user: loggedInUserId });
return tasks;
} catch (error) {
console.log(error);
throw error;
}
},
task: async ( parent, { id }, ) => {
console.log("taskbyId query, id : ", id);
// tasks.find(task => task.id == args.id);
try {
const task = await Task.findById(id);
console.log("taskById query, found task? : ", task);
return task;
} catch (error) {
console.log(error);
throw error;
}
},
},
Mutation: {
// createTask: combineResolvers(isAuthenticated, async (_, { input }, { email }) => {
createTask: async (_, { input }, { email }) => {
try {
console.log("creating task, email : ", email);
const user = await User.findOne({ email });
const task = new Task({ ...input, user: user.id });
const result = await task.save();
user.tasks.push(result.id);
await user.save();
return result;
} catch (error) {
console.log(error);
throw error;
}
}
// )
},
Task: {
user: async ( parent ) => {
console.log("in task.user field resolver");
try {
const user = await User.findById(parent.user);
return user;
} catch (error) {
console.log(error);
throw error;
}
}
},
}
When I run the tasks query, the console.log from the context setup function logs 3 times, but does NOT log the console.log line from the tasks resolver. It also appears to not return at all. I'm just using the default graphiql web client. The verifyUser() does find a return a user, so I know the db connection is working fine as well.
mergeResolvers should be used to merge resolvers.
It's designed to merge different [entities] object [/structured] resolvers before use [as one tree structured] in server [config].
F.e. it merges/combines respectively [by type] Query resolvers from users resolver with tasks Query resolvers ... and Mutation resolvers from users resolver with tasks Mutation resolvers.

Mongoose doesn't execute save and doesn't display error

Hi i have the following code:
export function createProduct(req, res) {
console.log("Execution")
const product = new Product({ ...req.body })
product.save(function (err, product) {
if (err) {
console.log("error")
const errorResponse = {}
for (let key in err.errors) {
//ValidationError handler
if (err.errors[key].properties) {
errorResponse[key] = err.errors[key].properties.message
}
//CastError handler
else {
errorResponse[key] = err.errors[key].toString().split(":")[1]
}
}
return res.status(400).send({ ...errorResponse })
}
console.log("created")
return res.send({ product })
})
}
There is no error on express side, console.log("Execution") is working and display this message correctly. I tested this by using Postman, when i send some data, response never come and on Postman there is error: "Error: socket hang up".
I've made console.log for req.body, and this is my output:
{
name: 'Apple Iphone 11 Pro 64GB Space Gray',
category: 'smartphone',
price: 4699,
inMagazine: { blocked: 0, inStock: 40 },
shortDescription: 'Odkryj wszystkie zalety iPhone 11 Pro 512 GB Silver. Smartfona, który zawstydza podkręconą wydajnością. Posiada bowiem najszybszy w historii procesor A13 Bionic oraz baterię, która pozwala na wiele. Weź iPhone 11 Pro do ręki i rób zdjęcia, których nie powstydziłby się nawet profesjonalista. Teraz masz do tego odpowiednie narzędzie – nowy iPhone 11 Pro posiada potrójny aparat główny, działający w oparciu o uczenie maszynowe. Efekty swojej fotograficznej przygody wraz z najmniejszymi detalami możesz ocenić z kolei na olśniewającym ekranie Super Retina XDR.',
images: [ { order: 1, src: '' } ]
}
Right after that, I found that I would check why it hangs, I had no error in the nodejs console. So I added two console.log to the code:
console.log("error")
console.log("created")
But both doesn't execute. So i made some code refactor and this works the same like above:
export async function createProduct(req, res) {
try {
const product = await Product.create({ ...req.body })
console.log("created")
return res.send({ product })
} catch (err) {
console.log("error")
const errorResponse = {}
for (let key in err.errors) {
//ValidationError handler
if (err.errors[key].properties) {
errorResponse[key] = err.errors[key].properties.message
}
//CastError handler
else {
errorResponse[key] = err.errors[key].toString().split(":")[1]
}
}
return res.status(400).send({ ...errorResponse })
}
}
I don't know what the cause of this problem could be.
This is link to whole project: https://github.com/codemasternode/DietShopping
Assuming you know that if you are saving the products like this all the keywords in the req.body should be the same as in the Product schema.
This should work:
exports.createProduct = async (req, res) => {
try{
const product = new Product(req.body).save();
return res.json(product);
}catch(err){
const errorResponse = {}
for (let key in err.errors) {
if (err.errors[key].properties) {
errorResponse[key] = err.errors[key].properties.message
}
else {
errorResponse[key] = err.errors[key].toString().split(":")[1]
}
}
return res.status(400).send({ ...errorResponse })
}
}
Try getting rid of the return(s) in front of the res. function calls
Like this:
export async function createProduct(req, res) {
try {
const product = await Product.create({ ...req.body })
console.log("created")
res.send({ product })
} catch (err) {
console.log("error")
const errorResponse = {}
for (let key in err.errors) {
//ValidationError handler
if (err.errors[key].properties) {
errorResponse[key] = err.errors[key].properties.message
}
//CastError handler
else {
errorResponse[key] = err.errors[key].toString().split(":")[1]
}
}
res.status(400).send({ ...errorResponse })
}
I've had an pre save in "products" model that doesn't let me go through. I was copying and pasting from other model and forget to remove unecesarry code.
I suggest you get rid of that callbacks and use clean async-await. By using this codes will be shorter too. And I think this will work. Try this.
export async function createProduct(req, res) {
try{
console.log("Execution")
const product = new Product({...req.body})
let result = await product.save()
console.log("created",result)
return res.send({ product })
}catch(err){
const errorResponse = {}
for (let key in err.errors) {
if (err.errors[key].properties) {
errorResponse[key] = err.errors[key].properties.message
}
else {
errorResponse[key] = err.errors[key].toString().split(":")[1]
}
}
return res.status(400).send({ ...errorResponse })
}
}

Retrieve the GET query string parameters using Express

I seem to have troubles on getting the query string parameter on my Postman.
First, I wanted to get all of the game types API by using the url of:
localhost:3000/api/gameType/dota2
Here is the code below:
const router = require('express').Router();
const GameTypeRepository = require('../../repository/GameTypeRepository');
router.get('/', async (req, res) => {
try {
const game_types = await GameTypeRepository.findByName(req.query.name);
res.json(game_types);
} catch (error) {
console.log(error);
res.sendStatus(500);
}
});
GameTypeRepository.js
const BaseRepository = require('../../../shared/repository/BaseRepository');
const GameType = require('../models/GameType');
class GameTypeRepository extends BaseRepository {
constructor(model) {
super(model);
}
findByName(name, fields) {
const options = {
where: { name }
};
if (!!fields && fields) {
options.attributes = fields;
}
return this.model.findOne(options);
}
}
module.exports = new GameTypeRepository(GameType);
But when I execute the url to my Postman, I get this log on my terminal that says:
Executing (default): SELECT `id`, `name`, `description` FROM `game_types` AS `game_types` WHERE `game_types`.`id` = 'dota2';
Which should be 'name' = 'dota2'
Any ideas on how to work with this? TYIA.
I have solved this problem by adding /type in my router.get('/type/:name,
router.get('/type/:name', async (req, res) => {
try {
const game_types = await GameTypeRepository.findByName(req.params.name);
res.json(game_types);
} catch (error) {
res.sendStatus(404);
}
});

How to update document by ID without using model

I have Card model and I have an API where I'm looking for a document by ID.
app.post("/api/postcomment", async (req,res) => {
const data = req.body
const reqUrl = req.headers.referer
const re = new RegExp('([a-zA-Z0-9]*$)', 'i')
const fixedUrl = reqUrl.match(re)
try {
await Card.update({_id: fixedUrl}, {$push:{'comments': data}})
const card = await Card.findById(fixedUrl)
return res.json(card)
} catch (err) {
throw err
}
})
It works fine. But now I have few more models. All should work the same way to them. But how can I make this code reusable for every model?
Or maybe there is a way to pass a name of my model to API? and then use it like this:
app.post("/api/postcomment", async (req,res, modelName) => {
const data = req.body
const reqUrl = req.headers.referer
const re = new RegExp('([a-zA-Z0-9]*$)', 'i')
const fixedUrl = reqUrl.match(re)
try {
await modelName.update({_id: fixedUrl}, {$push:{'comments': data}})
const item = await modelName.findById(fixedUrl)
return res.json(item )
} catch (err) {
throw err
}
})
Solution1: You can create two helper functions and call the from the router. Both function accept the model object:
let updateDocument = (model, fixedUrl, data) => {
return model.update({ _id: fixedUrl }, { $push: { comments: data }})
}
let getDocument = (model, fixedUrl) => {
return model.findById(fixedUrl)
}
app.post("/api/postcomment", async (req, res, modelName) => {
const data = req.body
const reqUrl = req.headers.referer
const re = new RegExp('([a-zA-Z0-9]*$)', 'i')
const fixedUrl = reqUrl.match(re)
try {
await updateDocument(Card, fixedUrl, data)
const item = await getDocument(Card, fixedUrl)
return res.json(item )
} catch (err) {
throw err
}
})
Solution2: The much better solution is to create a base class (service), with the common functionality. And extend it for each model:
class BaseService {
constructor(model) {
this.model = model;
}
getDocument(data) {
return this.model.findOne(...);
}
updateDocument(data) {
return this.model.update(...);
}
}
class CardService extends BaseService {
constuctor() {
super(Card);
}
}

Resources