axios.put is not working when sending query object - node.js

I was trying to send put request using axios to update information about a book in mongodb, the code is working fine when using postman but it is not working using axios.put used inside react component when submitting, while axios.delete is working fine. I think the problem is that sending the query object in that way is not the right way but I am not able to find the solution.
This is the function that handleSubmit,
the 'id' is the id of the book
'updates' is a state object that contains all the changes that should happen in the book data.
And the second function handleChange is the function that setupdates according to changes in the inputs
const handleSubmit = async (e) => {
e.preventDefault();
try {
const res = await axios.put("http://localhost:8000/book/edit/" + id, {
params: {
updates,
},
});
setNewBook(res.data)
} catch (err) {
console.log(err);
}
};
const handleChange = (e) => {
e.preventDefault();
const value = e.target.value;
setUpdates({
...updates,
[e.target.name]: value,
});
};

maybe you misunderstood the axios put method. as per the axios docs provided (https://github.com/axios/axios#axiosputurl-data-config) the correct structure is axios.put(url[, data[, config]])
The first parameter is URL
The second parameter is data (body request).
3rd parameter is config (you leave params in this parameter)
It should be like this
const res = await axios.put("http://localhost:8000/book/edit/" + id, null, {
params: {
updates,
},
});
I hope this can help you

Related

Fetching param.id data from front to backend - React / Node / Express

I am trying to return a user list on a component from my backend and I am having trouble passing my userID param back. I am using useEffect to pull the data from Redux then call an action on the component with what I think is the declared param. The correct userID is being passed correctly as far as I can see however I think the error is occuring when the route is passed the param.
In my action how should I pass the param of the userID that I want to get the data for? I have console.log the param.id/param.userID/userID etc. In the component I have the user.userID (from useSelector) however in the action folder I don't know how to pass it to the backend.
Also in the backend do I always have to set my params as id? can these have the same name as the value on the front-end such as 'userID'? I can only seem to get the backend Postman calls working with :id.
component
const user = useSelector(state => state.auth.user);
const [userDiveLog, setUserDiveLog] = useState({
user: [],
userDiveLogList: [],
expanded: false
})
// get access to dispatch
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchUserDiveLog(user.userID));
}, []);
action
// pulls the user dive log
export function fetchUserDiveLog(params, credentials, diveLogUserList){
return function(dispatch){
return axios.get("http://localhost:5002/api/divelog/userdiveloglist/" + params.userID)
.then(({data}) => {
dispatch(userDiveLogList(data));
});
};
}
Backend
route
// return an individual dive log
app.get('/api/divelog/userdiveloglist/:userID', controller.userDiveLog);
controller
exports.userDiveLog = (req, res, params, userID) => {
try {
const userID = req.params.userID
diveLog.findAll({
include: {all: true},
where: {diverUserNumber: userID}
})
.then(diveLog => {
const userDiveLogList = [];
for (i = 0; i < diveLog.length; i++) {
userDiveLogList.push(diveLog[i].dataValues);
}
if (!userDiveLogList) {
return res.status(404).send({message: "No dive logs belonging to this user"});
}
res.status(200).send({
data: userDiveLogList
})
})
} catch(err) {
res.status(500).send({
message: "Error retrieving dive log belonging to user id= " + id
});
}
};
Frontend
You're passing the user id (user.userID - a string) to the fetchUserDiveLog function but you're treating it like a params object with the userID property inside the function. params.userID returns undefined since params is a string (it holds the user id). Rename params to userId and add it to the URL.
You can also remove the credentials and diveLogUserList arguments from the fetchUserDiveLog function since they aren't used.
export function fetchUserDiveLog(userId) {
return (dispatch) => {
return axios
.get(`http://localhost:5002/api/divelog/userdiveloglist/${userId}`)
.then(({ data }) => {
dispatch(userDiveLogList(data))
})
}
}
Btw, you shouldn't hardcode the API URL. Use environment variables. If you're using Create React App, you can add environment variables prefixed with REACT_APP_ to .env or you can use dotenv-webpack if you have a custom Webpack setup.
Backend
There are a few issues with the backend code.
The userDiveLog function receives the next function as the third argument but it is named params which is confusing. Since you don't need the next function in the request handler, you should remove the params and userID arguments from the function. You can get access to userID from the req.params object which you're doing correctly.
exports.userDiveLog = (req, res) => {
// ...
}
The if (!userDiveLogList) condition will never be true since userDiveLogList is an array which is truthy in JavaScript. You can actually remove the if block. A response of { data: [] } will be sent if the user doesn't have any Divelogs which is perfectly okay. You can also omit the status(200) call since the status is automatically set to 200. And you can refactor the code by using object destructuring and Array.prototype.map to transform the divelogs.
const { userID } = req.params
const diveLogs = await diveLog.findAll({
include: { all: true },
where: { diverUserNumber: userID },
})
const data = diveLogs.map((log) => log.dataValues)
res.send({ data })
The catch block references the variable id which isn't defined anywhere. It should be userID instead.
The whole code using async/await:
exports.userDiveLog = async (req, res) => {
const { userID } = req.params
try {
const diveLogs = await diveLog.findAll({
include: { all: true },
where: { diverUserNumber: userID },
})
const data = diveLogs.map((log) => log.dataValues)
res.send({ data })
} catch () {
res.status(500).send({
message: `Error retrieving dive log belonging to user id= ${userID}`,
})
}
}
I have identified two questions after reading the description and I am going to answer each of those at a time.
Do I have to set the query parameter as Id?
No, there are no restrictions on the name of query parameter. You can literally name it as "something".
That said, there are some conventions and those dictate that you need to name the parameter to something that is appropriate.
How do I pass userId to my Action Creator?
First of all check the Function that is wrapping your Thunk. It expects 3 parameters: params (POORLY NAMED), credentials and diveLogUserList.
Where as, it is being dispatched with only 1 argument: userID.
Reconfigure this Wrapper Function to just receive the userID as an argument (and send credentials, diveUserList as an extra argument to the Thunk and not the wrapper function; This depends upon the functionality that you desire which is not properly understandable using the Description that you have provided).
After you reconfigured the wrapper function, you will dispatch the same like this:
fetchUserDiveLog(userID).
The Function handling your Route is incorrect
If I am not mistaken, controller.userDiveLog should only receive 3 arguments yet, you have defined your handler with 4 parameters.
The arguments that your handler should expect are: request, response and next.
The User ID that your handler expects will be a query parameter and will be accessible using: request.params.userID.
There is no need to expect userID as an argument to your handler.
Additional Information
I recommend going through these to get a better explanation and along with that, I recommend use of console.log as a method to debug the code. It will help you identify problems such as:
How many arguments is this function receiving?
What is the type of the argument received?
And much more
References
Route Function or Route Handler
Route Parameters
Sending Extra Argument to a Thunk

Node.js backend return response before all the API calls within the endpoints are made

I have a GET endpoint, which basically makes some API calls to the Spoonacular API. Essentially, I make two API calls within the endpoint.
The first API call gets the list of recipe ID's for the specific ingredients
The second API calls gets the metadata for each of the recipe ID's.
After the first API call I store all the Id's in an array (recipeArray), and I want to make the second api call for each ID in my array (function recipeTest does this).
When I try to do this and then return my response to the front end, it always returns a response before completing all the API calls in the second step.
Here, is my code. The first API calls works just fine, but the second API call (recipeTest function), is where it messed up. Before that function finishes making all the API calls to the Spoonacular API, my endpoint returns an empty Array (res.send(toSend)). So, I was just wondering if there is any way around this?
Thank you so much in advance, I really appreciate it!
module.exports = (app) => {
app.get('/api/search', async (req, res) => {
console.log("endpoint working");
let ingredientList = "apples,+eggs,+bacon"; // needs to be given from the front end
let ingredientSearchUrl = `https://api.spoonacular.com/recipes/findByIngredients?ingredients=${ingredientList}&number=1&ignorePantry=true&apiKey=${keys.spoonacularKey}`;
try {
const ingredientSearchResult = await axios({
method: 'get',
url: ingredientSearchUrl
});
var recipeArray = ingredientSearchResult.data.map(info => {
return info.id;
});
} catch (err) {
console.log("error in finding recipe ID ", err);
}
let toSend = [];
try {
const check = await recipeTest(recipeArray, toSend);
} catch (err) {
console.log("error in finding recipe information ", err);
}
res.send(toSend);
});
}
const recipeTest = async (recipeArray, toSend) => {
return Promise.all(
_.forEach(recipeArray, async (recipeId) => {
let recipeInfoUrl = `https://api.spoonacular.com/recipes/${recipeId}/information?includeNutrition=false&apiKey=${keys.spoonacularKey}`;
let recipeInfo = {};
const recipeData = await axios({
method: 'get',
url: recipeInfoUrl
});
// console.log("recipeInfo search working", recipeData.data);
recipeInfo['id'] = recipeData.data.id;
recipeInfo['title'] = recipeData.data.title;
recipeInfo['time'] = recipeData.data.readyInMinutes;
recipeInfo['recipeUrl'] = recipeData.data.sourceUrl;
recipeInfo['imageUrl'] = recipeData.data.image;
// console.log('recipe info dict', recipeInfo);
toSend.push(recipeInfo);
console.log('toSend inside', toSend);
})
);
}
_.forEach return collection itself and not all your async handlers.
Use recipeArray.map to get an array of async functions to let Promise.all do its work:
Promise.all(
recipeArray.map(x => async (recipeId) => {

Express Knex req.query

express and knex are beating me a little; I can't make this endpoint work using req.querys (response from express), even though I made one with req.params and it was ok.
Express:
app.get(`/actor`, async (req: Request, res: Response) => {
try {
// const { gender } = req.query;
const count = await getActorsByGender(req.query.gender as string);
console.log({ count });
res.status(200).send({ quantity: count, });
} catch (error) {
res.status(200).send({ message: error.sqlMessage || error.message });
}
});
Knex requisition:
const getActorsByGender = async (gender: string): Promise<any> => {
try {
const result = await connection.raw(`
SELECT COUNT(*) as count FROM Actor
WHERE gender = "${gender}"
`);
// console.log(`Temos: ${result[0][0].count} ocorrĂȘncias`);
return result;
} catch (error) {
console.log(error);
}
};
This might be because of the count(), but I'm not sure. The knex part is ok; I can console.log() the result.
The express part showed a empty object on insomnia
Using 'male' as parameter it was expected to return "2" as result.
Your sending male as a route/path parameter since you use http://localhost:3000/actor/male.
If you want to access it as a query-param, you can leave your code as it is, but you need to change your request-url to http://localhost:3000/actor?gender=male
Note that ff you wanted to define gender as a route-parameter, you'd need to change your route-handler to app.get("/actor/:gender") and access it using req.params.gender.
You are using path param in insomnia, not query param
If you want to use query params, you have to remove /male from the URL, and add query param key and value below the URL.
You could also change the URL to locahost:3000/actor?gender=male

Using fetch data to post to API

I have fetch data I am trying to re-use this data to push to a DB that then triggers a stored procedure
I have tried reusing the variable on a new function and get undefined, I don't even know where to start if using the existing axios get.
getEFID = (e) => {
e.preventDefault();
const EFID = e.target.elements.EFID.value;
console.log(EFID);
axios.get(`/api/vWEFID/${EFID}`)
.then((res) => {
console.log(res);
const DAEFForm = res.data;
this.setState({
DAEFForm: res.data
})
console.log("Getting transport Information...", DAEFForm)
});
}
TransportEF = (e) => {
//const tEF =
this.setState({ show: false });
e.preventDefault();
const TransportEF = e.target.elements.getEFID.DAEFForm.data;
/*axios.get(`/api/tEFID/`)
.then((res) => {
console.log(res);
const TransportEF = res.data;
this.setState({
TransportEF: res.data
})*/
alert('Transporting EF!')
console.log(TransportEF);
// })
}
If I can get the data out of the getEFID to TransportEF or somehow use the existing getEFID to console log the data then I can uncomment the API thats already set to post the data to the DB.
If I understand the issue correctly, you want to use response data from the API call in getEFID function as a payload within the API call inside the TransportEF function.
Since you tagged React and I can see setState being called, and assuming this is properly bound in both cases, all you really need to reference that data within TransportEF is this.state.DAEFForm.
See: https://reactjs.org/docs/state-and-lifecycle.html

How to: GET Data from 2 APIs, compare, POST bool

I'm working on a project that requires me to:
GET IDs from API1, push the IDs into an array, then map over those IDs, using them for a second GET request, where IDs are used as params for API2 GET request, populates an array with IDs or N for "Not existing" -- this array is then called in:
A POST request. This post maps over the returned array from the GET request. IF the item is not "N", it POSTS to API1 with checked: true. IF the item is "N", it emails us telling us API2 is missing this project.
I want this system to automatically do a GET and POST every 2 hours, so I'm using setInterval (not sure this is the best idea). EDIT: Cron job would be a better solution.
I'm working with NodeJS, Express, Request-Promise, Async / Await.
Here is some of my pseudo code so far:
// Dependencies
const express = require('express');
const axios = require('axios');
const mailgun = require('mailgun-js')({ apiKey, domain });
// Static
const app = express();
app.get('/', (req, res, next) => {
// Replace setInterval with Cron job in deployment
// Get All Ids
const orders = await getGCloud();
// Check if IDs exist in other API
const validations = await getProjectManagementSystem(orders);
// If they exist, POST update to check, else, mailer
validations.map(id => {
if (id !== 'n') {
postGCloud(id);
} else {
mailer(id);
}
});
}
// Method gets all IDs
const getGCloud = async () => {
try {
let orders = [];
const response = await axios.get('gCloudURL');
for (let key in response) {
orders.push(response.key);
}
return orders;
} catch (error) {
console.log('Error: ', error);
}
}
// Method does a GET requst for each ID
const getProjectManagementSystem = async orders => {
try {
let idArr = [];
orders.map(id => {
let response = await axios.get(`projectManagementSystemURL/${id}`);
response === '404' ? idArr.push('n') : idArr.push(response)
})
return idArr;
} catch (error) {
console.log('Error: ', error);
}
}
const postGCloud = id => {
axios.post('/gcloudURL', {
id,
checked: true
})
.then(res => console.log(res))
.catch(err => console.log(err))
}
const mailer = id => {
const data = {
from: 'TESTER <test#test.com>',
to: 'customerSuppoer#test.com',
subject: `Missing Order: ${id}`,
text: `Our Project Management System is missing ${id}. Please contact client.`
}
mailgun.messages().send(data, (err, body) => {
if (err) {
console.log('Error: ', err)
} else {
console.log('Body: ', body);
}
});
}
app.listen(6000, () => console.log('LISTENING ON 6000'));
The TL;DR: Need to do a GET request to API 1, then another GET request to API 2 following it (using IDs from API 1 as params), then send data from second GET to a POST request that then either updates API 1's data or emails Customer support. This is an automatic system that runs every two hours.
Main Questions:
1. Is it okay to have a setInterval in a get req?
2. Can I have a GET request automatically call a POST request?
3. If so, how can I pass GET request data onto a POST request?
To make it work for both of your calls one post and one get you have to do an Ajax call to get post processed information in another method.
I hope this works.

Resources