I'm trying to do a PUT request to an update controller from a react form (Mongoose API). Everything is passing over to the request, except the body. Now, this is my first time using FormData, so I'm almost positive that is where the issue lies, but I can't seem to sort out where the problem is..
The Submit action from the form
const clickSubmit = () => {
// console.log('Values on submit before FormData: ', values) // Shows the state object as expected
let userData = new FormData()
values.name && userData.append('name', values.name)
values.email && userData.append('email', values.email)
values.password && userData.append('password', values.password)
values.about && userData.append('about', values.about)
values.photo && userData.append('photo', values.photo)
update({
userId: match.params.userId
}, {
t: jwt.token
}, userData).then((data) => {
if (data && data.error) {
setValues({...values, error: data.error})
} else {
setValues({...values, 'redirectToProfile': true})
}
})
}
The Helper Method that set up the request
const update = async (params, credentials, user) => {
console.log('The params: ', params) // passes the user ID just fine
console.log('The credentials:', credentials) // passes the JWT just fine
console.log('The user object: ', ...user) // has all the information I'm updating, albeit in an array form that I can't really work with
try {
let response = await fetch('/api/users/' + params.userId, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer ' + credentials.t
},
body: user
})
return await response.json()
} catch (err) {
console.log(err)
}
}
And the controller I've commented out the rest of the logic to remove the clutter while I TS this issue
const update = async (req, res) => {
console.log(req)
const user = await User.findById(req.params.userId)
console.log('user after find: ', user) // returns the user that I want to modify from the database
console.log('body of request: ', req.body) // empty object
}
UPDATE:
I was able to get the FormData into an actual object using Object.fromEntries(user) - but it still won't pass into the request.. I have tried two ways:
const update = async (params, credentials, user) => {
console.log('The params: ', params)
console.log('The credentials:', credentials)
console.log('The user object: ', ...user)
let infoToUpdate = Object.fromEntries(user)
console.log('infoToUpdate: ', infoToUpdate);
try {
let response = await fetch('/api/users/' + params.userId, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer ' + credentials.t
},
body: {
"name": infoToUpdate.name,
"email": infoToUpdate.email,
"about": infoToUpdate.about
}
})
return await response.json()
} catch (err) {
console.log(err)
}
}
And
const update = async (params, credentials, user) => {
console.log('The params: ', params)
console.log('The credentials:', credentials)
console.log('The user object: ', ...user)
let infoToUpdate = Object.fromEntries(user)
console.log('infoToUpdate: ', infoToUpdate);
try {
let response = await fetch('/api/users/' + params.userId, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer ' + credentials.t
},
body: infoToUpdate
})
return await response.json()
} catch (err) {
console.log(err)
}
}
But req.body is still an empty object..
This has been solved, and it was all my flippin computer's fault..
On a whim, I killed node_modules and the package.lock files and reinstalled the deps.. and it started working.. My guess is that bodyParser didn't fully install..
Thank you all for the help.
Related
Recently updated SWR - now for some reason my data is not fetching properly.
const { data: expressionsData, error: expressionsError } = useSWRImmutable(
[`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
apiRequest
);
Using this fetching,
import firebase from "./firebase";
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
console.log("error", response);
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
// Create an Error with custom message and code
export function CustomError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
// Check if a indexDb database exists
export function indexedDbdatabaseExists(dbname, callback) {
const req = window.indexedDB.open(dbname);
let existed = true;
req.onsuccess = function () {
req.result.close();
if (!existed) window.indexedDB.deleteDatabase(dbname);
callback(existed);
};
req.onupgradeneeded = function () {
existed = false;
callback(existed);
};
}
Now I'm looking at this StackOverflow thread,
useSWR doesn't work with async fetcher function
And thinking I'll just remake the fetcher to be without Async. I'm just wondering why this has stopped working though in general, and if I can just keep my existing codebase.
The error is a 400 message, it only happens with this expressions API call which takes longer to load due to the amount of data I think,
xxxx/dashboard/expression/get-expression-analytics?startTime=1648183720488&endTime=1650865720488 400 (Bad Request)
with error log
These calls are working fine, they have substantly less data though.
const { data: overall, error: psychometricError } = useSWRImmutable(
`dashboard/psychometric/get-psychometric-home?starttime=infinite`,
apiRequest
);
const { data: sentimentData, error: sentimentError } = useSWRImmutable(
[`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
fetchSentiment
);
Made an update to the fetch call to be more readable and specifically about the URL pathway.
import firebase from './firebase';
// Create an Error with custom message and code
export function CustomError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
export async function expressionsRequest(path, method = 'GET') {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
return fetch(`/api/${path}`, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => {
if (!response.ok) {
throw `Server error: [${response.status}] [${response.statusText}] [${response.url}]`;
}
return response.json();
})
.then((receivedJson) => {
if (receivedJson.status === 'error') {
// Automatically signout user if accessToken is no longer valid
if (receivedJson.code === 'auth/invalid-user-token') {
firebase.auth().signOut();
}
throw new CustomError(receivedJson.code, receivedJson.message);
} else {
return receivedJson.data;
}
})
.catch((err) => {
console.debug('Error in fetch', err);
throw err;
});
}
Additionally, this is what the lambda function (using next API folder) looks like,
const requireAuth = require('../../_require-auth');
const { db } = require('../../_sql');
export default requireAuth(async (req, res) => {
const { uid: id } = req.user;
const startTime = Math.round(req.query.startTime * 0.001);
const endTime = Math.round(req.query.endTime * 0.001);
const parameters = [id, startTime, endTime];
//sql injection definitely possible here, need to work out better method of dealing with this.
const sqlText = `SELECT a,b,c,d,e,f,g,h,i FROM tablename WHERE a=$1 AND i BETWEEN $2 AND $3;`;
try {
const { rows } = await db.query(sqlText, parameters);
return res.status(200).json({
code: 0,
data: rows,
});
} catch (error) {
return res.status(200).json({
code: 0,
message: 'Error occurred in getting tablename',
error,
});
}
});
using postman with the same query, i.e.,
curl --location --request GET 'http://localhost:3000/api/dashboard/expression/get-expression-analytics?startTime=1648387240382&endTime=1651069240382' \
--header 'Authorization: Bearer xxxx' \
--data-raw ''
Successfully returns a response with data attached.
Based on your first code blocks, the startDate value is getting passed into the fetcher as method, and the endDate value is getting passed into the fetcher as data. This is based on the useSWR docs about passing in an array for the key argument: https://swr.vercel.app/docs/arguments#multiple-arguments
If the code you provided is correct, I'd assume the 400 is coming from trying to pass in a random value for the method option for fetch.
This should be fixed by only passing the API endpoint path into useSWR instead of an array:
const { data: expressionsData, error: expressionsError } = useSWRImmutable(
`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`,
apiRequest
);
We are building a serverless service to perform user management by using node-auth0 sdk but it cannot work as well as the direct api call.
Basic AWS serverless set up:
//aws lambda
exports.handler = async (event) => {
const auth0Management = await createAuth0Management();
let response;
const requestBody = JSON.parse(event.body.toString());
const createUserResponse = await createAuth0User(auth0Management, requestBody);
response = buildResponse(200, createUserResponse);
return response;
};
function buildResponse(statusCode, body){
return {
statusCode: statusCode,
headers:{
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}
}
Wait auth0 sdk to generate the client:
async function createAuth0Management(){
return new ManagementClient({
domain: process.env.DOMAIN,
clientId: process.env.CLIENTID,
clientSecret: process.env.CLIENTSECRET,
scope: process.env.SCOPE
});
}
We followed the Auth0 Gitghub docs to call Auth0 management API V2 throught the created auth0Management:
async function createAuth0User(auth0Management, userInfo){
const body = {
email: userInfo[0],
username: userInfo[1],
password: userInfo[2],
connection: 'xxx'
};
//sdk cannot create user
await auth0Client.createUser(body)
.then(function (response){
console.log(`create user success: ${response}`); // <- can not see in cloudwatch
})
.catch(function (err) {
console.log(`create user error: ${err}`); // <- can not see in cloudwatch
});
}
However, we can create the user by calling the api directly
async function createAuth0User(auth0Management, userInfo){
const body = {
email: userInfo[0],
username: userInfo[1],
password: userInfo[2],
connection: 'xxx'
};
//api can create user
await doPostRequest(body).then(()=> {
console.log('created'); // <- it works
})
}
const doPostRequest = (body) => {
return new Promise((resolve, reject) => {
const options = {
host: 'xxx',
path: '/api/v2/users',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization':"Bearer xxx"
}
};
//create the request object with the callback with the result
const req = https.request(options, (res) => {
resolve(JSON.stringify(res.statusCode));
});
// handle the possible errors
req.on('error', (e) => {
reject(e.message);
});
//do the request
req.write(JSON.stringify(body));
//finish the request
req.end();
});
};
Could anyone give us some help on this, please? Thanks.
I have created a backend middleware which checks if a user is logged in or not.
This functions handle the login route and initialise the req.session.
router.post("/login", upload.none(), async function (req, res) {
console.log("API");
const curr_email = req.body.email
const curr_password = req.body.password
let login_res = await User_functions.loginUser(req, curr_email, curr_password)
if (login_res == -1 || login_res == false) {
return res.send({error:"Wrong username or password"})
}
req.session.LoggedIn = true
req.session.user_id = login_res['user_id']
console.log("Successfully logged in !!!")
return res.redirect("/users")
});
This is the middleware which helps with authentication.
const requiresLogin = (req, res, next) => {
console.log("req.session",req.session)
if (req.session && req.session.user_id) {
return next();
} else {
console.log("User not logged in")
return res.status(400).json({error:'You must be logged in to view this page.'})
}
}
Now, using postman, this works as expected. User can only access specific URL's when logged in. But when I fetch the same URL using React js, after login in it fails. I can't figure out the reason for this.
This is my react js login control.
handleSubmit(event) {
event.preventDefault();
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: this.state.username,password : this.state.password })
};
let first = process.env.REACT_APP_URL
let second_arg = "login"
let url = first + second_arg
fetch(url, requestOptions)
.then(response => response.json())
.then(data => {
if('error' in data){
alert("Failed");
}
else{
// let history = useHistory();
// this.history.push("/dashboard");
this.props.history.push('/dashboard');
}
})
}
After redirecting if I try to access a protected route, I get alert that user is not logged in.
componentDidMount(){
const requestOptions = {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify()
};
const userId = this.props.match.params["user_id"];
let first = process.env.REACT_APP_URL
const url = first + "users/" + userId
console.log(url)
fetch(url, requestOptions)
.then(response => response.json())
.then(data => {
if('error' in data){
alert(data['error']);
}
else{
console.log(data)
this.setState({ user: data })
}
})
}
Stuck on this problem for 2 days now. Don't know what I am missing here. Can't find a reason for this anywhere on the internet also. Most folks have used JWT.
The complete frontend code is here :- https://github.com/dhruvdesmond/nutrify_frontend.
And backend is at :- https://github.com/dhruvdesmond/nutrify_backend
I am trying to do two post requests in JavaScript (client-side), but the second one seems to run before the first one is completed.
This is the code I have:
Client-side code:
$.post("/startGame", {username: username});
$.post("/lookingForAPlayer", {});
Server-side code:
var idPlayer;
app.post('/startGame', function(req, res) {
const idPlayerProm = new Promise((resolve, reject) => {
dbConnection.getIdPlayer(req.body.username).then(data => {
resolve(data)
});
});
Promise.all([idPlayerProm]).then(data => {
idPlayer = data[0];
console.log("idPlayer1: " + idPlayer) //Here idPlayer has a value
const gamePromise = new Promise((resolve, reject) => {
dbConnection.addGame(idPlayer).then(data => {
resolve(data);
});
});
Promise.all([gamePromise]).then(data => {
idGame = data[0];
});
})
});
app.post('/lookingForAPlayer', function(req, res) {
console.log("idPlayer2: " + idPlayer); //Here idPlayer is undefined
});
Result:
As you can see, it even prints idPlayer2 at first when should be printed after idPlayer1.
I am guessing I need to do some sort of Promise in the client side, but I am not sure how as I am new at doing them on the client-side.
Any help?
Update for #DanMossa (I cannot get the value I send from the server with his/her answer).
app.post('/startGame', function(req, res) {
dbConnection.getIdPlayer(req.body.username).then(data1 => {
dbConnection.addGame(data1).then(data2 => {
res.end(JSON.stringify([data1, data2]));
});
});
});
app.post('/lookingForAPlayer', function(req, res) {
//Here I will also res.end() as I do in /startGame.
});
Working off of #Yousaf 's post, using async/await might make it easier to mentally visualize.
try {
const res = await fetch('/startGame', {
method: 'POST',
body: JSON.stringify({ username: username }),
headers: { 'Content-Type': 'application/json' },
});
await fetch('/lookingForAPlayer', {
method: 'POST',
body: JSON.stringify({}),
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.log(error);
}
You can use promise chaining along with fetch api
fetch('/startGame', {
method: 'POST',
body: JSON.stringify({username: username}),
headers: { 'Content-Type': 'application/json' }
})
.then(res => {
// do something with response
// make second request
return fetch('/lookingForAPlayer', {
method: 'POST',
body: JSON.stringify({}),
headers: { 'Content-Type': 'application/json' }
});
})
.then(res => { /* do something with response */ })
.catch(err => console.log(err));
Keep in mind that second POST request will be sent only after first POST request is successful and client has received the response from the server
I'm learning nodejs and trying to make an API call. The API uses JWT to authenticate.
I created these functions to sign a token:
function token() {
const payload = {
iat: Math.floor(new Date() / 1000),
exp: Math.floor(new Date() / 1000) + 30,
sub: "api_key_jwt",
iss: "external",
jti: crypto.randomBytes(6).toString("hex")
};
return new Promise((resolve, reject) => {
jwt.sign(payload, privatekey, { algorithm: "RS256" }, function(
err,
token2
) {
if (err) reject(err);
else resolve(token2);
});
});
}
exports.genToken = async function() {
const header = {
"x-api-key": api
};
const data = {
kid: api,
jwt_token: await token()
};
async function authorization(req, res) {
try {
const auth = await rp({
url: authurl,
method: "POST",
headers: header,
body: data
});
res.send(auth.body);
} catch (error) {
res.send(404).send();
}
}
return {
"x-api-key": api,
Authorization: "Bearer " + authorization()
};
};
This works fine. Then I created a function to make the API call:
const token = require("./index").genToken;
const rp = require("request-promise");
exports.getOrderBook = function(res, error) {
const full_url = url + "order_book";
const auth = token();
rp({
url: full_url,
method: "GET",
headers: auth,
body: {
market: "btceur"
},
json: true
})
.then(function(response) {
res(response);
})
.catch(function(err) {
error(err);
});
};
And I call it using Express:
routes.get("/orderbook", async (req, res, next) => {
try {
const book = await orders.getOrderBook();
res.send(book);
} catch (error) {
next(error);
}
});
However, when I call my API, it shows an error in console:
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be one of
type string or Buffer. Received type object.
I guess the error is something with the token generation, because if I console.log(auth) in the getOrderBook function, it shows Promise { <pending> }, so probably an object is being passed as the jwt token.
Is it really the problem? I tried a lot of different solutions that I found on internet, however the concept of Async/Await is new to me, and I'm having some troubles to figure it out.
Thanks a lot in advance guys!
Since getToken is an anync function, the return is wrapped in a Promise as well so you would need another anync/await:
exports.getOrderBook = async function() {
let response;
try {
const full_url = url + "order_book";
const auth = await token();
response = await rp({
url: full_url,
method: "GET",
headers: auth,
body: {
market: "btceur"
},
json: true
});
} catch (e) {
// handle error
throw e
// or console.error(e)
}
return response;
};
In this line as well Authorization: "Bearer " + authorization(), authorization is returning a promise
const bearer = await authorization()
return {
"x-api-key": api,
Authorization: "Bearer " + bearer
};
For error handling wrap entire thing in try..catch block
exports.genToken = async function() {
try {
const header = {
"x-api-key": api
};
const data = {
kid: api,
jwt_token: await token()
};
async function authorization(req, res) {
let auth;
try {
auth = await rp({
url: authurl,
method: "POST",
headers: header,
body: data
});
// res object not available
// res.send(auth.body);
} catch (error) {
// res object not available, better throw error and handle in your middleware
// res.send(404).send();
}
return auth
}
const bearer = await authorization()
} catch (e) {
// handle error
}
return {
"x-api-key": api,
Authorization: "Bearer " + bearer
};
}