My front end is done in React Native and backend in nodejs. It is an application that asks the user to register (email, password, name, email, ...) and then the data is sent to a database (mongodb) using mongoose.
In my front end; when the user presses the button SignUp, it will call a function names "Submit" that you can find below:
const Submit = async (fname, email, pwd, confpwd) => {
if (String(pwd).localeCompare(String(confpwd)) == 0) {
let result = await fetch('http://127.0.0.1:2000/api/user/register', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(
{
name: fname.fname,
email: email.email,
password: pwd.pwd
}
)
})
console.log("RESULT : " + JSON.stringify(result))
} else{
console.log('NOT SAME PASSWORD')
}
};
It just takes in the firstname, email and password entered by the user and uses fetch to post it to the API. Everything works, except the last line : console.log("this is the result : " + JSON.stringify(result)). It always returns an empty json.
The register route in my backend is the following:
//REGISTER
router.post('/register', async (req, res) => {
//Check the input of the user before we make a user
const {error} = registerValidation(req.body)
if (error) {
console.log('Error1')
return 'Error1'
}
console.log('1&')
//Check if the user is already in the database
const emailExist = await User.findOne({email : req.body.email});
if(emailExist) {
console.log('Error2')
return 'Error2'
}
console.log('2&')
//Hash the password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(req.body.password, salt)
console.log('3&')
//Create a new user
const user = new User({
name: req.body.name,
email: req.body.email,
password: hashedPassword
})
console.log('4&')
//user.save();
try{
const saveUser = await user.save();
res.send(saveUser);
console.log('5&')
}catch(err){
res.send(err)
}
});
Before saving it to the databse it checks the validation and if the email already exists in the database.
It successfully sends the data to the database when the first two conditions are met and if email is already used and the validation format is not correct it does not put it in the dataabse.
However, when the validation format is not correct or the emaoil is already used, I would like my fetch in my front end to let me know. So I thought that putting my fetch in a variable it would output something if it did not work. However, it always sends back an empty json even when the fetch did not work. So, how can I pass a variable from my backend to my frontend ?
Normally in postman this is what I receive when the email already exists. How can I receive this on my frontend ?
On your backend,
You should return a valid HTTP status code and json payload in order to be correctly understood by your front-end.
For invalid inputs you can return a 400 Bad Request with a proper json description and return 200 OK for valid responses.
// for errors
res.status(400).send({error: 'Email already exists' })
// for successful responses
res.status(200).send({result: 'success' })
In your front-end, then you can parse this status code to understand if it is a valid / invalid request and parse payload to show any proper message to your users:
const Submit = async (fname, email, pwd) => {
try {
let response = await fetch('http://127.0.0.1:2000/api/user/register', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(
{
name: fname.fname,
email: email.email,
password: pwd.pwd,
},
),
});
const statusCode = response.status;
const { error, result } = response.json();
// do whatever you want with this info here
} catch (err) {
console.log(err);
}
};
Also, keep in mind that since fetch is running asynchronously, you should use await prefix in an async function like above or use Promises:
fetch returns a promise you need to handle it use async/await
const Submit = = async (fname, email, pwd) => {
const body = JSON.stringify({
name: fname.fname,
email: email.email,
password: pwd.pwd
})
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
}
const settings = {
method: 'POST',
headers,
body
};
try {
const fetchResponse = await fetch(`http://127.0.0.1:2000/api/user/register`, settings);
const data = await fetchResponse.json();
return data; // your response data
} catch (e) {
console.log(e)
return e; // handle your error here
}
}
fetch API in submit function is async means console.log() get execute before fetch complete.
you can use asyn/await to get fetch response and get that response to console.log().
// async function
async function fetchAsync () {
// await response of fetch call
let response = await fetch('https://api.github.com');
// only proceed once promise is resolved
return response;
}
Related
Problem
Hello,
I would like to know how I can create an Endpoint that first uses the POST method to login and obtain the token and then use that Token and use it in a GET method to access some data.
As you will see in the code, I currently have the logic of the POST apart and the GET apart, but my intention is to be able to have an endpoint that uses both methods.
The idea would be that after the POST method returns the token to me, I can use it later in the GET method.
I will appreciate your help and your prompt response!
code
app.post('/api/datas/login', async(req, res) =>{
const url = '...';
const options = {
email: process.env.EMAIL,
password: process.env.PASSWORD
}
const call = await axios.post(url, options)
const token = call.status === 200 ? call.data.token : null;
res.send({
status: call.status,
message: 'Logged In'
})
});
app.get('/api/datas/alldata', async(req, res) =>{
try {
const url = '...'
const call = await axios.get(url,{
headers: {
"Authorization" : `Bearer ${token}` //I need to use the token value from the POST method here!
}
});
const data = call.status === 200 ? call.data : null;
console.log(data);
res.status(200).json(data);
} catch (error) {
res.status(500).send({ message: error });
}
})
Yes, it should be possible (though not recommended) if you use app.all() instead of post() or get().
You can get the method of the request and act accordingly by looking at req.method.
For more info: https://expressjs.com/en/guide/routing.html
Here is my router
router.post("/login", async (req, res) =>
{
try
{
const user = await User.findByCredentials(req.body.email, req.body.password)
// console.log(user)
const token = await user.generateAuthToken()
// console.log(token)
res.redirect("/takvim")
}
catch(e)
{
res.status(400).redirect("/")
}
})
Here is my user model that I use in the function above
UserSchema.methods.generateAuthToken = async function ()
{
const user = this
const token = jwt.sign({_id: user._id.toString()}, "secret")
user.tokens = user.tokens.concat({token})
await user.save()
return token
}
UserSchema.statics.findByCredentials = async function (emails, passwords)
{
const user = await User.findOne({email: emails})
console.log(user)
const isMatch = await bcrypt.compare(passwords, user.password)
if(!isMatch)
{
throw new Error("unable to login")
}
return user
}
I am making the request from frontend using a button
$uyeolForm.addEventListener("submit", () =>
{
if(!$uyeolFormEmail.value.includes(".com"))
{
return $uyeolFormHata.innerHTML = "email geçersiz"
}
const xhr = new XMLHttpRequest();
let form = JSON.stringify({
email: $uyeolFormEmail.value,
password: $uyeolFormPassword.value
});
xhr.open("POST", "/login")
xhr.setRequestHeader('Content-type', 'application/json')
xhr.send(form);
})
Problem is when I am using the postman, application redirects me to the page i want and doesn't give an error.
When I send the request with button it still finds user but it doesn't redirect me to the page I expect and in the console i see the user(expected) and null which is not expected.
Thanks to everyone.
You are making an HTTP request with XMLHttpRequest when a submit event is triggered but you aren't preventing the default behaviour of a form submission.
So the XMLHttpRequest object is created and makes a request and then immediately after (and possibly cancelling the request depending on how quickly things go) the <form> is submitted to the URL specified in the action.
You said the endpoint was being hit twice, once where you get the user you expect and ones where you don't.
When you get the user you expect it is from the XHR submission.
When you don't, that is from the regular form submission (which won't be JSON encoded as HTML forms don't support JSON encoding so it doesn't find the user because it doesn't decode the data in the form correctly).
Since you said you wanted to redirect, don't use Ajax. Ajax is a method for making an HTTP request without leaving the current page.
Change the server-side code to accept the data in the format the <form> is encoding it (probably application/x-www-form-urlencoded unless you changed it with the enctype attribute).
You want to know what's the error message, always. Add a console.error(JSON.stringify(e))
before the response, and tell us what does it say.
catch(e)
{
console.error(JSON.stringify(e));
res.status(400).redirect("/");
}
If You're going to use application/json and application/x-www-form-urlencoded to support both ajax and usual form submission way - You've to redirect it on frontend level by reading Location header:
$uyeolForm.addEventListener("submit", (event) => {
event.preventDefault();
if(!$uyeolFormEmail.value.includes(".com")) {
return $uyeolFormHata.innerHTML = "email geçersiz"
}
fetch('/login', {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: $uyeolFormEmail.value,
password: $uyeolFormPassword.value
})
})
.then(function(response) {
if (response.redirected) {
const redirectTo = response.headers.get('Location');
if (redirectTo) {
window.location.href = redirectTo;
return;
}
}
})
.catch(function(error) {
alert(error.message);
});
})
keep in mind that to support both application/json and application/x-www-form-urlencoded You've to attach 2 body parsers as middleware:
const bodyParser = require('body-parser');
router.use(bodyParser.urlencoded(true));
router.use(bodyParser.json());
The fetch works if I just use the URL without /"some ID" at the end, but I want to target a sepcific id. I think the problem is from the clientside, because router.patch() works with postman (using the same URL). However I am not able to send any data to the server because the fetch cant find the URL (404). Or maybe fetch doesent work with patch requests, idk?
client-side:
async function update(){
const fromInp = { username: inp.value };
let param = {
method: 'PUT', // or 'POST'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(fromInp),
}
await fetch(`http://localhost:8080/api/userModel/5e4ec9567566adade97df11e`, param)
}
server-side:
router.patch('/:postId', async (req,res)=>{
try{
const update = await User.updateOne(
{_id: req.params.postId},
{ $set: {username: req.body.username}})
res.json(update)
} catch(err){
res.json({message: err})
}
})
Set the method property to be the text: 'PATCH'.
I have been trying to make a login page in reactjs but it's throwing me an error in console like
SyntaxError: Unexpected token r in JSON at position 0 but I got 200 status code in network tab and also I'm getting "redirect" in both response and preview tab under the network tab.
I tried the same code(except it was if(response.ok) this time) with another server of my friend and it successfully redirects it to another page
This is the code that I've been trying: is response.data not correct for reactjs?
performLogin = async () => {
var body = {
password: this.state.password,
email: this.state.email
};
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(body)
};
const url = "/api/authenticate";
try {
const response = await fetch(url, options);
const result = await response.json();
console.log(response); //nothing is showing in console for this statement
if (response.data == "redirect") {
this.props.history.push(`/verifyOtp/${this.state.email}`);
} else {
console.log("login failed");
window.alert("login failed");
}
} catch (error) {
console.error(error);
}
};
edit: I also tried it in postman and it gives "redirect" as response in postman so the api must be working fine
Your problem is in this line
const result = await response.json();
Your response is ok, everything is ok, but when you try to do response.json() and the response from the request isn't a valid json (maybe just a normal text), it will give you that error.
Because response can be a text or a json, you need to do some checking. Where is how to check if response is a json
This is kind of bad because on every request you will need to do this type of checking (transform it to text, try to parse, bla bla...), so What I recommend it you to use something better than fetch.
Axios is very good because it already do that checking.
For your example:
performLogin = async () => {
var body = {
password: this.state.password,
email: this.state.email
};
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(body)
};
const url = "/api/authenticate";
try {
const response = await fetch(url, options); // Fetch the resource
const text = await response.text(); // Parse it as text
const data = JSON.parse(text); // Try to parse it as json
// Do your JSON handling here
} catch(err) {
// This probably means your response is text, do you text handling here
}
}
I am using the ConnectyCube React Native SDK and have obtained an app auth token using their API. This token is required when making further requests - for example when logging in as a user. Their documentation says:
Upgrade session token (user login)
If you have an application session token, you can upgrade it to a user session by calling login method:
var userCredentials = {login: 'cubeuser', password: 'awesomepwd'};
ConnectyCube.login(userCredentials, function(error, user) {
});
The problem is it that when I use this method, I get an error in response saying 'Token is required'.
If I were interfacing with a REST API, I would put the token in the header of the request, but obviously in this instance I can't. So the question is, where do I put the token? I have it, the documentation just doesn't tell you how to use it! Any help appreciated.
Ok I came up with a fix. First of all I just tried passing the auth token in to the userCredntials object in the same way as in the documentation for social auth, that is absent from the description in my above code snippet taken from their docs.
Then I Promisified the API calls from within useEffect inside an async function to make sure everything was happening in the right order, and it works:
export default function App() {
const createAppSession = () => {
return new Promise((resolve, reject) => {
ConnectyCube.createSession((error, session) => {
!error
? resolve(session.token)
: reject(error, '=====1=====');
});
})
}
const loginUser = (credentials) => {
return new Promise((resolve, reject) => {
ConnectyCube.login(credentials, ((error, user) => {
!error
? resolve(user)
: reject(error, '=====2=====');
}));
})
}
useEffect(() => {
const ccFunc = async () => {
ConnectyCube.init(...config)
const appSessionToken = await createAppSession();
const userCredentials = { login: 'xxxxx', password: 'xxxxxxx', keys: { token: appSessionToken } };
const user = await loginUser(userCredentials);
console.log(user);
}
ccFunc()
}, []);
Hope it works....
please implement it by yourself...just take an understanding from code below.
code says: send the username and password to api...if all ok then authenticate else throw error ...if all ok..then store the returned token is asyncStorage...you can create the storage by any name you like...and use the token eveywhere in your app.
SignInUser = async () => {
this.setState({
username: this.state.username,
password:this.state.password,
})
if(this.state.username && this.state.password !== null){
try{
this.setState({
loading:true
})
const response = await fetch('YOUR API', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: this.state.username,
password: this.state.password
})
});
var promiseResponse = await response.json()
console.log(promiseResponse.token);
try {
await AsyncStorage.setItem('STORE_YOUR_LOGIN_TOKEN_HERE', JSON.stringify(promiseResponse.token));
console.log('Token Stored In Async Storage');
let tokenFromAsync = await AsyncStorage.getItem('STORE_YOUR_LOGIN_TOKEN_HERE');
console.log('Getting Token From Async...')
tokenFromAsync = JSON.parse(tokenFromAsync)
if(tokenFromAsync !== null){
console.log(tokenFromAsync);
this.setState({
loading:false
})
this.props.navigation.navigate('Tabnav');
}
} catch (error) {
// saving error
console.log(`ERROR OCCURED ${error}`)
}
//this.props.navigation.navigate('Tabnav')
} catch(error){
console.log(`COULDN'T SIGN IN ${error}`)
}
} else {
this.setState({
msg:'Invalid Credentials',
label:'red'
});
}
}
This is how i got the login to work in their sample react native app 1. i created a credentials object like this in my custom login function in src>components>AuthScreen>AuthForm.js
var credentials = {id:'',login: this.state.login,password: this.state.password}
2.I used their _signIn(credentials) function and set the 'id' attribute of my credentials object after their UserService.signin(credentials) resolved with a user object. (the resolved user object contained the logged-in user's id i.e user.id). Then it worked. This is how the code looked for the signin after the little tweak.
loginUser() { //my custom signin function
var credentials = {id:'',login: this.state.login,password: this.state.password} //my credentials object
this._signIn(credentials)
}
_signIn(userCredentials) { //their signin function
this.props.userIsLogging(true);
UserService.signin(userCredentials)
.then((user) => {
userCredentials.id = user.id //setting id of my credentials object after promise resolved
ChatService.connect(userCredentials) //using my credentials object with id value set
.then((contacts) => {
console.warn(contacts)
this.props.userLogin(user);
this.props.userIsLogging(false);
Actions.videochat(); //login worked
})
.catch(e => {
this.props.userIsLogging(false);
alert(`Error.\n\n${JSON.stringify(e)}`);
})
})
.catch(e => {
this.props.userIsLogging(false);
alert(`Error.\n\n${JSON.stringify(e)}`);
})
}