React : Cannot fetch data if multiple conditions on API - node.js

I'm going insane over this one. The api data is returning correctly when using postman, but for some reason in React I cannot get the data. While experimenting I noticed that if I pass only one condition in the api I will get the response back in React, but when passing two I cannot. Keep in mind this is only happening in react, so the API in postman is returning the response perfectly.
React Snippet:
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const { data: response } = await axios.get('/api/activity/suggested');
setData(response);
} catch (error) {
console.error(error);
}
setLoading(false);
};
fetchData();
}, []);
Mongoose api :
router.get('/suggested', auth, async (req, res) => {
try {
const user = await User.findById(req.user.id);
const activity = await Activity.find({
event: 'suggest_to_user',
user: req.user.id,
})
.sort({ date: -1 })
.populate([
{
path: 'user',
},
{
path: 'ref_following',
},
{
path: 'ref_request',
},
{
path: 'ref_review',
populate: [
{
path: 'suggestion',
},
],
},
{
path: 'ref_collection',
populate: [
{
path: 'user',
},
],
},
{
path: 'ref_suggestion',
},
]);
res.json(activity);
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
});
Removing a condition here make it work in React:
const activity = await Activity.find({
event: 'suggest_to_user',
user: req.user.id,
})

I think the problem is that when you call the API from your to react app. It doesn't find the id of the user. Try to send authorization user info through the header. Hope you find this helpful.

Related

MongoDB writing conflict for simple session-based parallel request

I have an Express based CRUD application which uses MongoDB as its DB. I have noticed that some concurrent writes fail if they use bulkWrite and session.
A simplified example looks like this:
import express from 'express';
import { v4 } from 'uuid';
import mongoose from 'mongoose';
const router = express.Router();
const mongoString = 'mongodb://127.0.0.1:27017/testMongo?directConnection=true';
const port = 3288;
const testId = mongoose.Types.ObjectId();
const Model = mongoose.model('Data', new mongoose.Schema({
whateverString: {
required: true,
type: String,
},
}));
mongoose.connect(mongoString);
const database = mongoose.connection;
database.on('error', (error) => {
console.log(error);
});
database.once('connected', async () => {
console.log('Database connected');
// Add test data if not exists
if (!await Model.exists({ _id: testId })) {
const data = new Model({
_id: testId,
whateverString: v4(),
});
await data.save();
}
});
const app = express();
app.use(express.json());
router.post('/', async (req, res) => {
const session = await mongoose.startSession();
session.startTransaction();
try {
await Model.bulkWrite([
{
updateOne: {
filter: {
_id: testId,
},
update: {
whateverString: v4(),
},
},
},
], { session });
await session.commitTransaction();
res.status(200).json({ allRight: true });
} catch (error) {
await session.abortTransaction();
console.log(error.message);
res.status(400).json({ message: error.message });
} finally {
session.endSession();
}
});
app.use('/', router);
app.listen(port, async () => {
console.log(`Server started at ${port}`);
});
What this does is:
connecting to Mongo
creating a test document
creating a web server and one post route
if the post route is called, the test document is updated with a random string in a bulkWrite and a session
Now take a simple client script which does three requests in parallel:
import fetch from 'node-fetch';
function doFetch() {
return fetch('http://localhost:3288', { method: 'post' });
}
async function myMain() {
try {
const promises = [doFetch(), doFetch(), doFetch()];
const response = await Promise.all(promises);
console.log('response', response.map(resp => ({ status: resp.status, text: resp.statusText })));
} catch (error) {
console.log(error);
}
}
myMain();
The result is: Only one DB query works, whereas the others fail with the error WriteConflict error: this operation conflicted with another operation. Please retry your operation or multi-document transaction.
I am rather new to MongoDB and Mongoose but in my understanding of databases, such a use-case should be fine. (In this example, the requests would overwrite each other and create chaos, but in the real-life use case that should not be a problem at all.)
Some observations I've made:
Without passing session to bulkWrite, everything works fine.
It is obviously some kind of race condition: sometimes two queries go through, sometimes only one.
Setting maxTransactionLockRequestTimeoutMillis to 20000 did not help.
If I include the fetching in the server process itself (after console.log('Server started ...), then everything works fine. I cannot explain that to be honest.
What am I doing wrong? How can I solve that problem?
Appendix: The package.json file of that example looks like this:
{
"name": "rest-api-express-mongo",
"dependencies": {
"express": "^4.17.3",
"mongoose": "^6.2.2",
"node-fetch": "^3.2.10",
"uuid": "^9.0.0"
},
"type": "module"
}
``
Thanks to the comment provided by Marco Luzzara I was able to refactor and solve the issue via callbacks.
The code being now:
let retry = 0;
await database.getClient().withSession(async (session) => {
try {
await session.withTransaction(async () => {
await Model.bulkWrite([
{
updateOne: {
filter: {
_id: testId,
},
update: {
whateverString: v4(),
},
},
},
], { session });
await session.commitTransaction();
res.status(200).json({ allRight: true });
});
} catch (error) {
console.log(error.message);
res.status(400).json({ message: error.message });
retry += 1;
if (retry > 5) {
session.endSession();
}
}
});
Just for reference - the whole file looks now like:
import express from 'express';
import { v4 } from 'uuid';
import mongoose from 'mongoose';
const router = express.Router();
const mongoString = 'mongodb://127.0.0.1:27017/testMongo?directConnection=true';
const port = 3288;
const testId = mongoose.Types.ObjectId();
const Model = mongoose.model('Data', new mongoose.Schema({
whateverString: {
required: true,
type: String,
},
}));
mongoose.connect(mongoString);
const database = mongoose.connection;
database.on('error', (error) => {
console.log(error);
});
database.once('connected', async () => {
console.log('Database connected');
// Add test data if not exists
if (!await Model.exists({ _id: testId })) {
const data = new Model({
_id: testId,
whateverString: v4(),
});
await data.save();
}
});
const app = express();
app.use(express.json());
router.post('/', async (req, res) => {
let retry = 0;
await database.getClient().withSession(async (session) => {
try {
await session.withTransaction(async () => {
await Model.bulkWrite([
{
updateOne: {
filter: {
_id: testId,
},
update: {
whateverString: v4(),
},
},
},
], { session });
await session.commitTransaction();
res.status(200).json({ allRight: true });
});
} catch (error) {
console.log(error.message);
res.status(400).json({ message: error.message });
retry += 1;
if (retry > 5) {
session.endSession();
}
}
});
});
app.use('/', router);
app.listen(port, async () => {
console.log(`Server started at ${port}`);
});

How to send query parameters with Axios?

My question is similar to this How to post query parameters with Axios?
Instead of posting, I want a get data request and I wanna pass the query param name to the request. It works in postman but not working in react.
const handleSubmit = async () => {
try {
const res = await axios.get(
"http://localhost:5000/api/products",
{},
{
params: {
name,
},
}
);
console.log(res.data);
} catch (err) {}
};
exports.retrieveProducts = (req, res) => {
Product.find(
{ name: { $regex: req.query.name, $options: "i" } },
(err, products) => {
if (err) res.status(500).json(err);
res.json(products);
}
);
};
You are using an empty object as config.
It should be
const handleSubmit = async () => {
try {
const res = await axios.get(
"http://localhost:5000/api/products",
{
params: {
name: 'whatever',
},
}
);
console.log(res.data);
} catch (err) {}
};

Correct Async/Await usage with Axios

I'm working with axios to post user responses to a database. I'm using this set up shown below to handle many posts back to back. I'm wanting to make sure that this is the correct set up to avoid backing up requests.
Is this the correct way to use async and await when using Axios?
// Frontend React Code
// Posting conclusionInput to Mongodb
const postConclusion = async () => {
await axios({
method: "POST",
data: {
conclusion: conclusionInput,
},
withCredentials: true,
url: "http://localhost:4000/conclusion",
}).then((res) => console.log(res));
};
//Backend Node / Express code
app.post("/conclusion", (req, res) => {
console.log("Attempting to post the conclusion");
User.findOne({ username: req.user.username }, async (err, user) => {
if (err) throw err;
if (user) {
(user.conclusion = req.body.conclusion), await user.save();
res.send();
}
});
});
Frontend
In an async function use await and try/catch. Any .then calls can be rolled out into const x = await y variables.
Return values from promise functions, in case you want to use them.
const postConclusion = async () => {
const res = await axios({
method: "POST",
data: {
conclusion: conclusionInput,
},
withCredentials: true,
url: "http://localhost:4000/conclusion",
})
console.log(res)
return res
};
Backend
Again, if you are going with async use that API consistently.
Mongoose provides a promise API, so use that too.
app.post("/conclusion", async (req, res) => {
try {
console.log("Attempting to post the conclusion");
const user = await User.findOne({ username: req.user.username })
if (!user) {
return res.send('not found')
}
user.conclusion = req.body.conclusion
await user.save()
return res.send('saved')
}
catch (error) {
console.error(error)
return res.send('error')
}
});
When using async await, setting an await call to a variable is equal to the parameter in a .then callback
// Frontend React Code
// Posting conclusionInput to Mongodb
const postConclusion = async () => {
// Set the await call to a variable.
const res = await axios({
method: "POST",
data: {
conclusion: conclusionInput,
},
withCredentials: true,
url: "http://localhost:4000/conclusion",
})
// Now no need for .then()!! This is why async/await is so nice.
console.log(res)
};
//Backend Node / Express code
app.post("/conclusion", (req, res) => {
console.log("Attempting to post the conclusion");
User.findOne({ username: req.user.username }, async (err, user) => {
// You need to send the error to the request. Otherwise, the
// request will keep waiting for a response until it times out.
if (err) {
console.error(err)
res.status(500).send(err)
}
if (user) {
// Side note: these should all be on separate lines:
user.conclusion = req.body.conclusion
await user.save();
// You should also send a status code and a response message
res.status(200).send({status: "Success}");
}
});
});
I recommended have a folder called "services" and inside it has yours services by backend.
services/
getData.js
import axios from "axios";
export const getData = () => {
axios.post("http://localhost:4000/conclusion");
};
App.js
import { useEffect, useState } from "react";
import { getData } from "./services/getData";
export default function App() {
const [data, setData] = useState([]); // save the value of service
useEffect(() => {
try {
getData().then((res) => {
setData(res?.data);
});
} catch (e) {
console.error(e);
}
}, []); // execute once
return <div className="App">{data}</div>;
}

Mongoose.findOneAndUpdate is failing to run

So I'm working on a graphql server and I'm trying to make an async/await external call to facebook's server.
The issue is that all this information does come back but it does not save to the db.
How do I know it does come back? if I use findOne instead of findOneAndUpdate I can console.log all the info and see it successfully but once I switch back I don't even get an error.
I have looked at the mongoose docs and tried to apply the findOneAndUpdate properly but it just laughs at me no error it just doesn't do anything.
Any way here is my code if any one can give me some advice it would really be appreciated.
The first set is code using findOne which I know works 100% but does not save or update the doc.
The second one is when I try to do it with FindOneAndUpdate.
getFacebookPageID: {
type: FacebookType,
description: 'Gets all the content we want from facebook once a user has granted permissions',
args: {
id: { type: GraphQLString },
accessToken: { type: GraphQLString },
facebook: { type: InputFacebookType }
},
resolve: (parent, args) => User.findOne({ _id: args.id }, async (err, docs) => {
console.log('next step is getcontent');
// const clientId = process.env.FACEBOOK_ID;
// const reDirectFBUri = process.env.FACEBOOK_ID_URI;
const { accessToken } = docs.tokens.find((item) => item.kind === 'facebook');
const userId = docs.facebookId;
console.log(userId);
const getFBaccounts = `https://graph.facebook.com/${userId}/accounts?access_token=${accessToken}`;
let pages = null;
await axios.get(getFBaccounts)
.then((response) => {
pages = response.data.data;
// this above line is all the fb pages user has give us access to
console.log('pages', response.data.data);
const query = { _id: args.id, };
console.log('This should be object id', query);
User.updateOne(query, {
pages,
}, (err, docs) => {
console.log('Any errors here are problems with saving!', err, docs);
});
})
.catch((err) => console.log(err));
return FacebookType;
}
),
},
I have also tried this below and that simply will not even console log anything. This is me trying to use findOneAndUpdate
getFacebookPageID: {
type: FacebookType,
description: 'Gets all the content we want from facebook once a user has granted permissions',
args: {
id: { type: GraphQLString },
accessToken: { type: GraphQLString },
facebook: { type: InputFacebookType }
},
resolve: (parent, args) => User.findOneAndUpdate({ _id: args.id },{ pages: async (err, docs) => {
console.log('next step is getcontent');
const { accessToken } = docs.tokens.find((item) => item.kind === 'facebook');
const userId = docs.facebookId;
console.log(userId);
const getFBaccounts = `https://graph.facebook.com/${userId}/accounts?access_token=${accessToken}`;
let pages = null;
await axios.get(getFBaccounts)
.then((response) => {
pages = response.data.data;
console.log('pages', response.data.data);
})
.catch((err) => console.log(err));
return FacebookType;
}
},
{new: true}
),
},
you use async/await in mix with promises. You should choose one approach (I suggest async/await);
resolve: async (parent, args) => {
try {
//get user
const user = await User.findById(args.id); //user is mongooose instance - https://mongoosejs.com/docs/api.html#document_Document-save
const { accessToken } = user.tokens.find((item) => item.kind === 'facebook');
const userId = user.facebookId;
const getFBaccounts = `https://graph.facebook.com/${userId}/accounts?access_token=${accessToken}`;
//get pages
const fbResponse = await axios.get(getFBaccounts);
//use mongoose prototype method "save" to update the user
user.pages = fbResponse.data.data;
await user.save();
return user;
} catch(e) {
console.log(e);
}
}

The loader.load() function must be called with a value,but got: undefined

I am following this graphql tutorial, everything was going ok until I try to use dataloaders.
My server.js is:
const start = async () => {
const mongo = await connectMongo();
const buildOptions = async req => {
const user = await authenticate(req, mongo.Users);
return {
context: {
dataloaders: buildDataloaders(mongo),
mongo,
user
},
schema
};
};
app.use('/graphql', bodyParser.json(), graphqlExpress(buildOptions));
app.use(
'/graphiql',
graphiqlExpress({
endpointURL: '/graphql',
passHeader: `'Authorization': 'bearer token-name#email.com'`
})
);
app.use('/', expressStaticGzip('dist'));
app.use('/attendance', expressStaticGzip('dist'));
app.use('/login', expressStaticGzip('dist'));
spdy.createServer(sslOptions, app).listen(process.env.PORT || 8080, error => {
if (error) {
console.error(error);
return process.exit(1);
} else {
console.info(
`App available at https://localhost:${process.env.PORT || 3000}`
);
}
});
};
My copy and paste dataloaders.js:
const DataLoader = require('dataloader');
async function batchUsers(Users, keys) {
return await Users.find({ _id: { $in: keys } }).toArray();
}
module.exports = ({ Users }) => ({
userLoader: new DataLoader(keys => batchUsers(Users, keys), {
cacheKeyFn: key => key.toString()
})
});
And my resolvers.js:
export default {
Query: {
allLinks: async (root, data, { mongo: { Links } }) =>
Links.find({}).toArray()
},
Mutation: {
createLink: async (root, data, { mongo: { Links }, user }) => {
const newLink = Object.assign({ postedById: user && user._id }, data);
const response = await Links.insert(newLink);
return Object.assign({ id: response.insertedIds[0] }, newLink);
},
createUser: async (root, data, { mongo: { Users } }) => {
const newUser = {
name: data.name,
email: data.authProvider.email.email,
password: data.authProvider.email.password
};
const response = await Users.insert(newUser);
return Object.assign({ id: response.insertedIds[0] }, newUser);
},
signinUser: async (root, data, { mongo: { Users } }) => {
const user = await Users.findOne({ email: data.email.email });
if (data.email.password === user.password) {
return { token: `token-${user.email}`, user };
}
}
},
Link: {
id: root => root._id || root.id,
postedBy: async ({ postedById }, data, { dataloaders: { userLoader } }) => {
return await userLoader.load(postedById);
}
},
User: {
id: root => root._id || root.id
}
};
When I try get my allLinks I got the error:
TypeError: The loader.load() function must be called with a value,but
got: undefined.
Can anyone help me?
So I was able to reproduce the error by creating a link with a user, deleting the user from the Mongo database, and then querying for the postedBy attribute of the Link.
I would suggest dropping all your links and recreating your user (register + sign in), creating a new link, then querying for the postedBy field.

Resources