Handling error sent back by express in postman - javascript

I am working with postman where i am sending my post data to api to insert data in mongodb.My issue is that i am not able to handle the error message properly. Here is my code for it
exports.addUser = (req, res, next) => {
const db = getdb();
// console.log(db)
// console.log(db)
db.collection12("user").insertOne({
name: req.body.name,
password:req.body.password
}).then((result) => {
res.send(result)
}).catch((err) => {
res.status(404).send('Error in adding')
});
};
so i knowingly made and error and wrote "collection12" so that i get and error but then in my catch method i am returning
("Error in addding")
so why then in postman i am not able to see this instead of that i am seeing a hude error meassge that says
See error here
I guess than same would be the issue in my react side where instead of getting my own error meassge i would get this huge message

You actually don't enter the catch block because it cannot even execute the db call. If you want a 404 error to be dispatched in this situation you need to add a try/catch statement like this:
exports.addUser = (req, res, next) => {
const db = getdb();
// console.log(db)
// console.log(db)
try {
db.collection12("user").insertOne({
name: req.body.name,
password:req.body.password
}).then((result) => {
res.send(result)
}).catch((err) => {
res.status(404).send('Error in adding')
});
} catch {
res.status(404).send('Error in adding')
}
};

Related

How can I get custom HTTP error messages?

Here im making a request to from the frontend to backend:
await axios.post("http://localhost:5000/users/signin", data) // <- sending wrong data
.then()
.catch((err) => console.log(err.message)); // <-- here the response is 404
Here i make the request to the mongodb:
const signin = async (req, res) => {
const { email } = req.body;
try {
const existingUser = await User.findOne({ email });
if (!existingUser) return res.status(404).json({ message: "User not found." });
};
router.route("/signin").post(signin)
Bacically i want when the user sends wrong data i want the err.message i get to be "User not found." but it is Request failed with status code 404.
Why?
Because err.message is not where the JSON data is stored; Instead use err.response.data (err.response is the actual response from the web server, err is what is raised by the axios.post() failing)

Troubles with nodejs GET endpoint

I have the following endpoint:
app.get('/users/:id', async (req, res) => {
const _id = req.params.id;
try {
const user = await User.findById(_id);
if(!user) {
res.status(404).send();
}
res.send(user);
} catch (e) {
res.status(500).send(e);
}});
When I make the request with a valid user ID, the server sends back the user, no problem with that.
The problem is when I try to find a user with a ID which doesnt exist in the database. The server should response with a 404 Error but instead it sends back a Error 500 and I dont understand why!
Could anyone help me please?
Thank you in advance!
One nice way to handle the errors is to create an express error middleware, this allows you to put all of your error handling in one place so that you dont have to write it more than once.
With express when you use async routes handlers if a promise rejects the error will automatically be passed to the next error middleware.
// First register all of your routes
app.get('/user/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if(!user) return res.status(404).send();
res.send(user);
})
// Then register you error middleware
app.use((err, req, res, next) => {
console.error(err.message)
// if mongoose validation error respond with 400
if(err.message.toLowerCase().includes('validation failed'))
return res.sendStatus(400)
// if moongoose failed because of duplicate key
if(err.message.toLowerCase().includes('duplicate key'))
return res.sendStatus(409)
// if mongoose failed to cast object id
if(err.message.toLowerCase().includes('objectid failed'))
return res.sendStatus(404)
res.sendStatus(500)
})
Thank you for your answers.
I have solved it adding the following to the user model schema:
_id: {type: String}
And adding a return before sending the 404 error:
app.get('/users/:id', async (req, res) => {
const _id = req.params.id;
try {
const user = await User.findById(_id);
if (!user) {
return res.status(404).send();
}
res.send(user);
} catch (error) {
res.status(400).send(error);
}});

OPTIONS request inconsistency (in deployment)?

Introduction
So, I'm using the MERN stack (with Heroku + Netlify), and I'm having some really strange consistency problems with how the DELETE request is being handled. I've tried countless solutions in the last
three days trying to get this to work and none of them have worked. A lot of these solutions have
come from stack overflow, so if you want to direct me to another post, the chance is that I've already seen it. I've scoured every part of the web and making this post is my last resort.
The Problem
So, when I make a delete request, I'm getting the per-usual OPTIONS request since I'm sending a token in a custom header of the request ('x-auth-token'). The OPTIONS request always resolves with a 204, meaning that everything should be alright. However, afterward, there is no DELETE request like there should be. This, in essence, is my problem. I've checked my Heroku logs, and all I can see is the OPTIONS request, and nothing else.
Inconsistencies?
So this is where I've been very confused. The thing is, that sometimes it DOES work. And other routes I use in my API (like login, and creating a new post) work, even though I'm using the same middleware.
Every time it works, I get the OPTIONS request and then the DELETE request (with a 200 status) like I would expect to happen.
If you want an example of a re-creatable scenario:
I create X number posts after logging in and getting a valid token, then I can see those posts rendering in the posts listing on my home page. I then navigate one of the posts and delete it by clicking and then a confirmation button. I automatically get redirected to the next post in the list. I repeat this till I get to the last post. I delete that post, and since there are no more posts left, I get redirected to the posts listing which is... not empty! The last post I tried deleting is still there.
Keep in mind, that the DELETE requests all get sent in exactly the same way, so I'm pretty sure this isn't a front-end issue, so no need to poke around in the code there. I've logged everything and debugged, and it's 100% consistent with what I would expect.
(The create post doesn't redirect, while the delete post does? I don't see how this would effect anythign as the DELETE request gets sent as per usual... Though maybe a solution lies within this fact.)
Solutions I've tried
Cors
First off, you might already be rushing to your keyboard to tell me that this is a CORS issue. I thought the same thing yesterday, but I'm not so sure now. I've tried messing with all the config settings possible in CORS to get this to work. Since my two websites are on different domains, then CORS verifies the requests. I've already added my front-end website to a whitelist, and all other requests are going through properly, so no problem there. I've tried adding an allowHeaders option in the config, but it didn't do anything more than the default setting. I've also added 'OPTIONS' to the allowed methods in the config, still nothing. I'm also using app.use(cors({config})). I'll include some code later to see some more of this in detail.
Debugging
I've basically tested things out by inserting console.logs everywhere and discovered that neither the middleware, the options route (I tried making an options route with same route url), or the original post route get executed when the OPTIONS request doesn't result in a DELETE request.
Static Server
This is maybe where some of my inexperience shows (this is my first Web project). I saw some solutions telling that a static server is needed. So I tried setting up a static server, but I didn't see any results. So I'm not too sure what this accomplished.
Async and Await
I was just trying things at this point, so I made all my routes async to see if it would do anything. It didn't.
Others
I've also messed around with environment variables and dotenv, and other stuff I can't remember. I think everything here should already be sufficient information understand the situation.
Code
index.js
const express = require('express');
require("dotenv").config({ path: "variables.env" });
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const routes = require("./routes/router");
const cors = require("cors");
const morgan = require('morgan')
const app = express();
const whitelist = [
process.env.ORIGIN
];
app.use(
cors({
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
console.log(origin);
callback(new Error("Not allowed by CORS"));
}
}, //frontend server localhost:3000
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
credentials: true, // enable set cookie
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(morgan('dev'));
mongoose.connect(process.env.MONGODB_URL, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', () => {
console.log('connected to db');
});
const userSchema = mongoose.Schema({
name: String,
password: String
});
// Routes
// TODO: make seperate routers/routes
app.use("/", routes);
// Serve static assets if in production
if (process.env.NODE_ENV === 'production') {
// Set static folder
app.use(express.static('client/build'));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
// TODO: set up custom port in future
app.listen(process.env.PORT, () => console.log(`Server listening at http://localhost:${process.env.PORT}`));
// Callback functions?
router.js
const express = require('express');
const router = express.Router();
const Post = require('../models/Post');
const User = require('../models/User');
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const auth = require('../middleware/auth');
const adminAuth = require('../middleware/adminAuth');
const cors = require("cors");
require("dotenv").config({ path: "variables.env" });
// import 'moment'
// second onwards are handlers => triggers like the post body then next() to go to the next handler
router.post('/api/add_post', adminAuth, async (req, res, next) => {
try{
newPost = new Post({
title: req.body.title,
body: req.body.body,
author: req.body.author,
created: req.body.created,
});
const savedPost = await newPost.save();
if (!savedUser) throw Error('Something went wrong saving the post');
res.send(savedPost);
} catch (e) {
res.status(400).json({ msg: e.message });
}
});
router.delete('/api/delete_post/:id', adminAuth, async (req, res, next) => {
// timeout?
// console.log(req.body);
try{
const id = req.params.id;
if(!id) throw Error('Invalid ID');
const post = await Post.findById(id);
if (!post) throw Error('Post doesn\'t exist');
const removed = await post.remove();
if(!removed) throw Error('Problem with deleting the post');
res.status(200).json({ success: true });
} catch(e) {
console.log("Error: ", e.message);
res.status(400).json({ msg: e.message, success: false });
}
});
// TODO : UPDATE for async soon
router.post('/api/update_post', adminAuth, async (req, res, next) => {
const id = req.body._id;
test_post_data = {
title: req.body.title,
body: req.body.body,
author: req.body.author,
modified: req.body.modified,
};
console.log(test_post_data, id);
Post.updateOne({ _id: id }, test_post_data, (err) => {
if(err) return next(err);
return res.status(200);
});
});
router.get('/api/get_posts', async (req, res, next) => {
try{
const posts = await Post.find();
if(!posts) throw Error('Error with fetching the posts')
res.send(posts.reverse());
} catch (e) {
res.status(400).json({ msg: e.message });
}
});
router.get('/api/get_chapter/:id', async (req, res, next) => {
try{
const id = req.params.id;
const post = await Post.findOne({_id: id})
if(!post) throw Error('No post was found')
res.send(post);
} catch(e) {
res.status(400).json({ msg: e.message })
}
});
// User routes
// TODO : make in seperate file
router.post('/api/user/register', async (req, res) => {
const { name, email, password } = req.body;
// Simple validation
if (!name || !email || !password) {
return res.status(400).json({ msg: 'Please enter all fields' });
}
try {
const user = await User.findOne({ email });
if (user) throw Error('User already exists');
const salt = await bcrypt.genSalt(10);
if (!salt) throw Error('Something went wrong with bcrypt');
const hash = await bcrypt.hash(password, salt);
if (!hash) throw Error('Something went wrong hashing the password');
const newUser = new User({
name,
email,
password: hash,
admin: false
});
const savedUser = await newUser.save();
if (!savedUser) throw Error('Something went wrong saving the user');
// TODO : check up on expires stuff : 3600 = 1 hr
const token = jwt.sign({ id: savedUser._id, admin: savedUser.admin }, process.env.JWT_SECRET, {
expiresIn: 3600
});
res.status(200).json({
token,
user: {
id: savedUser.id,
name: savedUser.name,
email: savedUser.email,
admin: savedUser.admin
}
});
} catch (e) {
res.status(400).json({ error: e.message });
}
});
router.post('/api/user/login', async (req, res) => {
const { name, password } = req.body;
// Simple validation
if (!name || !password) {
return res.status(400).json({ msg: 'Please enter all fields' });
}
try {
// Check for existing user
const user = await User.findOne({ name });
if (!user) throw Error('User Does not exist');
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) throw Error('Invalid credentials');
const token = jwt.sign({ id: user._id, admin: user.admin }, process.env.JWT_SECRET, { expiresIn: 3600 });
if (!token) throw Error('Couldnt sign the token');
res.status(200).json({
token,
user: {
id: user._id,
name: user.name,
email: user.email,
admin: user.admin
}
});
} catch (e) {
res.status(400).json({ msg: e.message });
}
});
module.exports = router;
adminAuth.js
const jwt = require('jsonwebtoken')
require("dotenv").config({ path: "variables.env" });
module.exports = (req, res, next) => {
console.log(req.header('x-auth-token'));
const token = req.header('x-auth-token');
// Check for token
if (!token)
return res.status(401).json({ msg: 'No token, authorizaton denied' });
try {
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
console.log('decoded:', decoded);
if(!decoded.admin)
return res.status(401).json({ msg: 'Not an admin, authorization denied' });
// Add user from payload
// console.log('decoded:', decoded);
req.user = decoded;
next();
} catch (e) {
res.status(400).json({ msg: 'Token is not valid' });
}
};
Link for request examples, and Heroku log since Stackoverflow says it's spam:
https://gist.github.com/macklinhrw/b2fec97642882ba406c49cce3e195c39
Edit
I pasted the Chrome request and response headers into the gist at the bottom, but there was no response data to go along with either.
I've debugged a little using this to check the difference and I discovered that with delete action that ends up working, the red (canceled) request has headers, while the non-working is completely empty (filled with 'provisional headers' if that means anything).
I couldn't copy-paste the request headers into the gist for the working red (canceled) one. But, I pasted everything that I thought could possibly be useful from chrome, hopefully it helps.
Also, I didn't see any DELETE requests when I was using the Chrome network tool, and I was seeing them on the other tool. Not sure if it matters, probably just a config option somewhere.
So, I haven't found an exact answer, but I've found a workaround.
As it turns out, it might have something to do with axios, and I've been searching for the wrong things in the last 3 days.
This thread helped me: https://github.com/axios/axios/issues/1428
I've added an e.preventDefault() to the onClick method I use for the delete button.
This fixed the problem, but doesn't redirect (I use href={link}), so I'm going to add a conditional render for react-router to redirect the page. I don't know of a better method so maybe give me some ideas. I'll edit if I have further troubles.

Node.js Express execute inside app.post()

I have a problem right now that I can't solve by myself. I assume you know more here. I'm just getting started.
By using the following code I'm getting a list of customers:
app.get("/customers", customers.findAll);
I wanted to add authentication. But now I don't know how to execute "customers.findAll" and get the value as JSON.
app.get("/customers", verifyToken, (req, res) => {
jwt.verify(req.token, 'justAtest, (err, authData) => {
if (err) {
res.sendStatus(403);
} else {
// execute customers.findAll
}
});
});
Customers is integrated via a requirement
const customers = require("../controllers/customer.controller.js");
The contents are as follows:
exports.findAll = (req, res) => {
Customer.getAll((err, data) => {
if (err)
res.status(500).send({
message:
err.message || "Some error occurred while retrieving customers."
});
else res.send(data);
});
};
Do you have any ideas?
Thank you in advance.
Grettings
Rok
You achieve that using something called "middleware". Explore it since it is very important.
Basically :
app.get("/customers", verifyToken,customers.findAll);
Wherre verify token is a funtion that has 3 parameters: req, res and 3rd one called "next".
So your verify token function would look something like:
(req, res,next) => {
jwt.verify(req.token, 'justAtest, (err, authData) => {
if (err) {
res.sendStatus(403);
} else {
next();
}
});
}
I took it from your snippet. Basically if you want to jump to customeeers.finalAll, just call "next" and it jumps to next function :D.

Node.js error handling setup not working as intended

I am trying to have all my error messages in one file, each error is denoted by an error code, then in my functions/services, when there is an error, I call a function that takes the error code as an argument, then returns an object to the client with the error code and the respective error message from the errors.js file.
as an example, a user trying to register with an email that already exists in the database, here is how I try to do it:
// userService.js -- where my register function is
const { errorThrower } = require('../../utils/errorHandlers');
...
static async registerNewUser(body) {
const exists = await User.where({ email: body.email }).fetch();
if(exists) {
errorThrower('400_2');
}
...
}
errorHandlers.js file:
exports.errorThrower = (errCode) => {
throw Object.assign(new Error(errors[errorCode]), { errorCode })
}
exports.errorHandler = (err, req, res, next) => {
if(!err.status && err.errorCode) {
err.status = parseInt(err.errorCode.toString().substring(0, 3), 10);
}
let status, message
if (err.status) {
status = err.status
message = err.message
} else {
status = 500;
message = 'unexpected behavior, Kindly contact our support team!'
}
res.status(status).json({
errorCode: err.errorCode,
message
})
}
errors.js
module.exports = {
'400_1': 'JSON payload is not valid',
'400_2': 'user already registered',
...
}
...
const user = require('./routes/user');
const { errorHandler } = require('../utils/errors');
...
app.use('/user' , user);
app.use(errorHandler);
...
now with this setup, when hitting the register endpoint by postman, I only get the following in the console
UnhandledPromiseRejectionWarning: Error: user already registered
could someone please tell me what am I missing here?
thanks in advance!
You're not catching the error which you throw inside your errorThrower, thus getting the error UnhandledPromiseRejectionWarning. What you need to do is catch the error and pass it on the the next middleware, in order for the errorHandler-middleware to be able to actually handle the error. Something like this:
exports.register = async(req, res) => {
try {
await registerNewUser(req.body);
} catch(err) {
next(err);
}
};
If you don't want to do this for every middleware, you could create a "base"-middleware which handles this:
const middlewareExecutor = async (req, res, next, fn) => {
try {
return await fn.call(fn, req, res, next);
} catch (err) {
next(err);
}
};
Now you can pass your middlewares as an argument and delegate handling the error to the executor:
app.use('/user' , async (req, res, next) => middlewareExecutor(req, res, next, user));

Categories