I am making a user signup API, where name and email are set to be unique. When I send a JSON object which has a the same email address as an existing user, it gives an error as expected.
But there is no message in that error, which i can then use to give a more usable message.
Below is the error object that i'm getting.
{ "index": 0,
"code": 11000,
"keyPattern": {
"email": 1
},
"keyValue": {
"email": "pranatkarode#rocketmail.com"
}
}
And this is my controller module,
const User=require("../models/userModels.js");
exports.signUp=(req,res)=>{
const user=new User(req.body);
user.save((err,user)=>{
if(err){
res.send(err)
}
else{
res.json({
user
})
}
})
}
Mongoose's default error object is designed for non limited use cases. So you need to look at it and depending on what it contains add custom messages. For this specific use case of yours, you can for example do it like so:
const User = require("../models/userModels.js");
exports.signUp = async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.json({
user,
});
} catch (err) {
if (err.code === 11000 && err?.keyPattern.hasOwnProperty("email")) {
err.message = "This email is alrady used.";
} else if (err.code === 11000 && err?.keyPattern.hasOwnProperty("name")) {
err.message = "This name is alrady taken.";
}
res.send(err);
}
};
I'm using async/await syntax here to have a better looking code, yours would work as well.
The solution is quite simple, just remove the await before the await user.save and boom everything should be fine... but if problem persist then check your username and password in your .env file and make sure they are correct, also you should make sure your IP address is set to both 0.0.0.0 and your current IP in the atlas.
Before:
await user.save();
After:
user
Related
i am using graph api javascript example from here https://learn.microsoft.com/en-us/graph/api/user-list-joinedteams?view=graph-rest-beta&tabs=javascript
and my code is like:
async function(req, res) {
if (!req.isAuthenticated()) {
// Redirect unauthenticated requests to home page
res.redirect('/')
} else {
let params = {
active: { calendar: true }
};
// Get the access token
var accessToken;
try {
accessToken = await tokens.getAccessToken(req);
console.log("access token is:", accessToken)
} catch (err) {
req.flash('error_msg', {
message: 'Could not get access token. Try signing out and signing in again.',
debug: JSON.stringify(err)
});
}
if (accessToken && accessToken.length > 0) {
try {
console.log("vik testing stuff12 for teams")
const user = await graph.getTeams(accessToken)
console.log("graph me:::", user)
} catch (err) {
req.flash('error_msg', {
message: 'Could not fetch events',
debug: JSON.stringify(err)
});
}
} else {
req.flash('error_msg', 'Could not get an access token');
}
res.render('calendar', params);
}
}
getTeams is
getTeams: async function(accessToken) {
const client = getAuthenticatedClient(accessToken);
const events = await client
.api('/me/joinedTeams')
.version('beta')
.get();
return events;
}
this prints no results and no error. if I replace 'me/joinedTeams' to just 'me' then it returns logged in user details.
You can got a response successfully, so it seems no error with your code as you said if you call https://graph.microsoft.com/v1.0/me you can get user information.
And I tried to call this API using my account(my account hasn't joined any Teams), and got response like below, so if you got the same response as mine, perhaps you need to check if you have joined any Teams:
On the other hand, following the document, this API needs several permissions. So please obtain your access token when debug and use JWT tool to decrypt it to check if the access token have enough scope.
And I used the same request and got Teams information after adding my account to a team.
Im trying to do login/register module in my project. This is my login function. I would like to have one function that will validate all things for me so I dont have to use so many "if" statements. I was trying to do with pure function but completely don't know how to do it. Can someone help me ?
const loginUser = async (req, res, next) => {
const { password, email } = req.body;
if (!email) {
return res.status(400).json({
message: "Error: Email cannot be blank.",
});
}
if (!password) {
return res.status(400).json({
message: "Error: Password cannot be blank.",
});
}
try {
const user = await User.findOne({ email: email });
if (!user)
return res.status(400).json({
message: "Invalid user",
});
if (!validPassword(password, user.password))
return res.status(400).json({
message: "Invalid password",
});
const { name, likedArr, _id } = user;
const token = crypto.randomBytes(32).toString("hex");
const userSession = new UserSession({ userId: _id, token });
await userSession.save();
return res.status(200).json({
message: "Valid login",
token: token,
user: {
name,
likedArr,
userId: _id,
},
});
} catch (err) {
next(err);
}
};
Abstracting my comments into an answer.
On Pure Functions:
If I understand pure functions correctly, I don't think you can have a pure function that calls an external API which may fail, since the same inputs may possibly return different results depending on the external state of the API (unless that API is guaranteed pure itself somehow). (Definition of a pure function)
On Repetition:
I genuinely think you don't have a lot of repetition here. Your code is clear and only has 4 conditionals, all for things you need to test for. You could abstract the similarities of your JSON returns into something like a template string depending on the conditions, but I think that could add clutter and opacity to your code, which isn't a good trade-off if you do it too much.
If you want an example of what I mean here:
if (!email) {
return res.status(400).json({
message: "Error: Email cannot be blank.",
});
}
if (!password) {
return res.status(400).json({
message: "Error: Password cannot be blank.",
});
}
Can become...
if (!email || !password) {
return res.status(400).json({
message: `Error: ${!email ? 'Email' : 'Password'} cannot be blank.`,
});
}
I have the following code for signing up a user. Where I first validate the user input. Secondly I check to see if the user already exists, if yes it should return with response 400. If not go to step 3 and add the new user. Finally in step 4 return the newly created entry. Logically, it works and adds data to database correctly, however it always responds back with 'User already exists' on postman (from step 2) even if it's a new user which has correctly added the user to the db. Which makes me think the third step is being done before a response in step 2 can be sent, which would mean I have not chained the promise correctly. Also the new user is never sent back as response, which I think is because I have not used Promise.then() together with user.save() correctly. I also get the following error (posted after the code), which I understand means I am trying to send a second response after a first has already been sent. I can solve this problem with async and await but want to learn how to do it this way. Thanks, any help is appreciated.
const { User, validateUser } = require('../models/userModel');
const mongoose = require('mongoose');
const express = require('express');
const router = express.Router();
router.post('/', (req, res) => {
return Promise.resolve()
.then(() => {
//Step 1: validae the user input and if there is an error, send 400 res and error message
console.log('My user post body req::', req.body);
const { error } = validateUser(req.body); //this is using Joi.validate() which has a error property if errors are found
if (error) {
return res.status(400).send(error.details[0].message);
}
})
.then(() => {
//step 2: check if user already exists, if yes send res 400
let user = User.findOne({ email: req.body.email });
if (user) {
return res.status(400).send('User already exists');
}
})
.then(() => {
//Step 3: enter new user into the database
user = new User({
name: req.body.name,
email: req.body.email,
password: req.body.password
});
return user.save();
})
.then((result) => {
//step 4: return the newly added user
return res.status(200).send(result);
})
.catch((error) => {
console.log('Error Adding new User', error);
});
});
module.exports = router;
I get the following error message from the catch. Even though I am I am returning with every response
Error Adding new User Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:494:11)
at ServerResponse.header (/home/ssaquif/WebDevProjects/movie-reviews-backend/node_modules/express/lib/response.js:771:10)
at ServerResponse.send (/home/ssaquif/WebDevProjects/movie-reviews-backend/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/home/ssaquif/WebDevProjects/movie-reviews-backend/node_modules/express/lib/response.js:267:15)
at ServerResponse.send (/home/ssaquif/WebDevProjects/movie-reviews-backend/node_modules/express/lib/response.js:158:21)
at /home/ssaquif/WebDevProjects/movie-reviews-backend/routes/users.js:35:27
at processTicksAndRejections (internal/process/task_queues.js:93:5) {
code: 'ERR_HTTP_HEADERS_SENT'
You don't need to use Promise.resolve in your route.
You just need a chain of then blocks, in which you need to return a value to the next one.
I refactored your code like this:
router.post("/", (req, res) => {
//Step 1: validate the user input and if there is an error, send 400 res and error message
console.log("My user post body req::", req.body);
const { error } = validateUser(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
//step 2: check if user already exists, if yes send res 400
User.findOne({ email: req.body.email })
.then(user => {
if (user) {
return res.status(400).send("User already exists");
}
return;
})
.then(() => {
//Step 3: enter new user into the database
let user = new User({
name: req.body.name,
email: req.body.email,
password: req.body.password
});
return user.save();
})
.then(result => {
//step 4: return the newly added user
return res.status(200).send(result);
})
.catch(error => {
console.log("Error Adding new User", error);
res.status(500).send("Error");
});
});
You will have a result like this when a user successfully registers:
{
"_id": "5dd65df52f7f615d8067150d",
"name": "ssaquif",
"email": "test#test.com",
"password": "123123",
"__v": 0
}
And when an existing email is used, the response will be like this with statusCode 400.
User already exists
You could solve this somehow by chaining promises correctly in a more complicated way, or you use async / await and get rid of all those problems:
router.post('/', async (req, res) => {
try {
//Step 1: validae the user input and if there is an error, send 400 res and error message
console.log('My user post body req::', req.body);
const { error } = validateUser(req.body); //this is using Joi.validate() which has a error property if errors are found
if (error) {
return res.status(400).send(error.details[0].message);
}
//step 2: check if user already exists, if yes send res 400
let user = await User.findOne({ email: req.body.email });
if (user) {
return res.status(400).send('User already exists');
}
//Step 3: enter new user into the database
user = new User({
name: req.body.name,
email: req.body.email,
password: req.body.password
});
await user.save();
//step 4: return the newly added user
return res.status(200).send(user);
} catch(error) {
// Report error internally
return res.status(500).send("Something bad happened");
}
});
The main problem with your code is that returning from a .then callback will continue executing the next .then callback. Therefore you try to set the headers status multiple times (but that's your smallest problem).
If you look at the error message "Cannot set headers after they are sent to the client" it means you are trying to send something over the response object twice which are not possible. Try log something right before each time you send something as a response and see which two are being called.
Instead of returning the res.status(400).send promise, try call it normally and then return a rejected promise or throw an error instead.
I'd like to remove an object element from my user object, I'm using pull to remove it, but it returns
TypeError: user.company.pull is not a function
router.put('/reset', passport.authenticate('jwt', {session:false}), (req, res, next)=> {
user = req.user;
var id_student = user.id;
var id_company = user.company;
var results = [];
User.findById(id_student, function(err, user) {
if(err) {
console.log(err);
return res.status(500).send({message: "Error"});
}
if(!user) {
return res.status(404).send({message: "User Not Found"});
}
user.company.pull({'company': id_company});
res.send(user);
});
});
Effectively, user.company.pull is probably undefined, rather than the function that you're looking for.
If user.company doesn't exist, then user.company is going to be undefined, and there's not a pull method defined on undefined. This means that you're effectively trying to call undefined.pull({'company': whatever}), which will never work.
Try adding a guard to ensure that you have a company attached to a user, in the same way you check to ensure that the user exists. For example:
if (!user.company) {
return res.status(404).send({ message: 'Company not found'});
}
Use 'use strict'; at top of js files.
Read https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode for more information
Check if variable user has property copmany or not and handle it.
Use this condition
if(!user || !user.hasOwnProperty('company')) {
return res.status(404).send({message: "User Not Found"});
}
Seems like error messages are wrapped in text. Say in a model validation I just want to send "exists" to the client if a record already exists.
One the server maybe I do something like:
validate: {
isEmail: true,
isUnique: function (email, done) {
console.log("checking to see if %s exists", email);
user.findOne({ where: { email: email }})
.then(function (user) {
done(new Error("exists"));
},function(err) {
console.error(err);
done(new Error('ERROR: see server log for details'));
}
);
}
}
On the client maybe I do:
feathers.service('users').create({
email: email,
password: password
})
.then(function() {
console.log("created");
})
.catch(function(error){
console.error('Error Creating User!');
console.log(error);
});
The error printed to console is:
"Error: Validation error: exists"
How to I just send the word "exists" without the extra text? Really I'd like to send back a custom object, but I can't seem to find any examples of doing this. The closest I've seen is this: https://docs.feathersjs.com/middleware/error-handling.html#featherserror-api
But I haven't figured out how to make something like this work in the validator.
Feathers does not change any error messages so the Validation error: prefix is probably added by Mongoose.
If you want to change the message or send an entirely new error object, as of feathers-hooks v1.6.0 you can use error hooks:
const errors = require('feathers-errors');
app.service('myservice').hooks({
error(hook) {
const { error } = hook;
if(error.message.indexOf('Validation error:') !== -1) {
hook.error = new errors.BadRequest('Something is wrong');
}
}
});
You can read more about error and application hooks here