I have the following js express code:
app.get('/lists2', (req, res) => {
mongo.getDB()
.then(db => db.collection('dogs'))
.then(collection => collection.find().toArray())
.then(array => res.json(success(array)))
// How can I throw in the middle of a promise to trigger express's middleware?
.catch(error => {
throw {message: "database error"};
});
});
app.use(function (err, req, res, next) {
const message = err.message || 'Encountered a server error';
const status = err.status || 500;
res.status(status).json({status, message});
})
I have written a middleware error handler so I can trigger an API error response with throw the problem is I can't throw inside the then because it's inside async code, is there any method to get around this? Or is my error handling pattern incorrect?
You should use next (cf. doc):
app.get('/lists2', (req, res, next) => {
mongo.getDB()
.then(db => db.collection('dogs'))
.then(collection => collection.find().toArray())
.then(array => res.json(success(array)))
// How can I throw in the middle of a promise to trigger express's middleware?
.catch(error => {
next(new Error("database error"));
});
});
app.use(function (err, req, res, next) {
const message = err.message || 'Encountered a server error';
const status = err.status || 500;
res.status(status).json({status, message});
})
Related
Sorry if the title if confusing, I wasn't too sure how to word it. I have a PATCH request to update a value in my database, but even though it is "working" (200 status), it's not actually.
I have a .route('/:movie_id/:user_id').all() handler to trigger for all my methods, where it pulls a movie from the database by movie_id and user_id. This works. Then I move on to my PATCH request, but it seems like the PATCH request isn't actually running. I am getting the correct response from the .all() handler, but no update is happening. Even if I completely comment out the code for my PATCH, I am still getting a 200 status.
Here is my .all() handler with my PATCH request:
movieRouter
.route('/:movie_id/:user_id')
.all(requireAuth)
.get((req, res, next) => {
const db = req.app.get('db')
MovieService.getById(db, req.params.movie_id, req.params.user_id)
.then(movie => {
if(!movie) { // this runs fine
return res.status(404).json({ error: `Movie doesn't exist`})
}
// res.json({movie : movie}); --> old code
// solution:
res.movie = movie;
next();
return movie;
})
.catch(next)
})
.patch(requireAuth, (req, res, next) => {
const db = req.app.get('db')
const { watched } = req.body
const updatedMovie = { watched }
// this doesn't run
const numVal = Object.values(updatedMovie).filter(Boolean).length
if(numVal === 0) {
return res.status(400).json({ error: `Must not be blank`})
}
MovieService.updateMovie(db, req.params.movie_id, req.params.user_id, updatedMovie)
.then(movie => {
res.status(200).json(updatedMovie)
})
.catch(next)
})
Here is my MovieService:
updateMovie(db, movie_id, newMovie) {
return db('your_movie_list').where('id', movie_id).where('user_id', user_id).update(newMovie).returning('*')
}
It should be the problem of the 2nd .all(), .all() will catch all request, no matter it is GET, POST, PATCH, DELETE. So even when you comment out PATCH code, it will return 200.
Change the 2nd .all to .get like below
app.use(express.json())
movieRouter
.route('/:movie_id/:user_id')
.all(requireAuth)
.get((req, res, next) => { // use .get instead of .all to avoid catching all requests
const db = req.app.get('db')
MovieService.getById(db, req.params.movie_id, req.params.user_id)
.then(movie => {
if(!movie) { // this runs fine
return res.status(404).json({ error: `Movie doesn't exist`})
}
res.json({movie : movie});
})
.catch((e) => {
console.log("From getMovie", e);
res.status(400).json({ error: e.message })
})
})
.patch((req, res, next) => {
try {
const db = req.app.get('db')
const { watched } = req.body
const updatedMovie = { watched }
// this doesn't run
const numVal = Object.values(updatedMovie).filter(Boolean).length
if(numVal === 0) {
return res.status(400).json({ error: `Must not be blank`})
}
MovieService.updateMovie(db, req.params.movie_id, req.params.user_id, updatedMovie)
.then(movie => {
console.log(movie) // nothing logs
res.status(200).json(movie[0])
})
.catch((e) => {
console.log("From updateMovie", e);
res.status(400).json({ error: e.message })
})
}catch(e) {
console.log("From try/catch", e);
res.status(400).json({ error: e.message })
}
})
A little working example for cross-checking
const express = require("express");
const app = express();
const PORT = process.env.PORT || 8080;
app.use(express.json())
const movieRouter = express.Router()
movieRouter
.route('/:movie_id/:user_id')
// .all(requireAuth)
.get((req, res, next) => {
res.json({"movie:get" : 1});
})
.patch((req, res, next) => {
res.json({"movie:patch" : 1});
})
app.use(movieRouter)
app.listen(PORT, function (err) {
if (err) console.log(err);
console.log("Server listening on PORT", PORT);
});
I am having issues with middleware in my express app, I have the following route:
app.post(
'/api/auth/signup',
[
verifySignUp.checkDuplicateUsernameOrEmail,
verifySignUp.checkRolesExist
],
controller.signup
);
There are the two middleware checkDuplicateUsernameOrEmail and checkRolesExist as follows:
const checkDuplicateUsernameOrEmail = (req, res, next) => {
console.log('checkDuplicateUsernameOrEmail');
User.findOne({username: req.body.username}).exec()
.then(user => {
if (user) {
console.log("user name exists");
return fail(res, {message: 'This username already exists'});
}
return User.findOne({email: req.body.email}).exec()
})
.then(user => {
if (user) {
return fail(res, {message: 'This email already exists'});
}
next();
})
.catch(error => {
console.log(error);
fail(res, {message: 'Database internal error occured.'});
});
};
const checkRolesExist = (req, res, next) => {
console.log('checkRolesExist');
console.log(req.body.roles);
for (const role of req.body.roles) {
if (!ROLES.includes(role)) {
return fail(res, {message: `${role} is not a valid role`});
}
}
next();
};
const fail = (res, err) => {
const message = err.message || 'Encountered a server error';
const status = err.status || 500;
res.status(status).json({status, message});
}
I make a request with a username that has already been used before and in the console I get user name exists as expected however the app goes on to call checkRolesExist, shouldn't execution of middleware stop when it hits a return? What am I doing wrong where?
That's because return fail returns to... nothing. you're returning stuff inside the then() callback function, not inside checkDuplicateUsernameOrEmail(). So, the execution keeps going, and you hit the next .then().
Go async/await style, it will make your life easier :
const checkDuplicateUsernameOrEmail = async(req, res, next) => {
console.log('checkDuplicateUsernameOrEmail');
try {
if ( await User.findOne({ username: req.body.username }).exec() ) {
console.log("user name exists");
return fail(res, { message: 'This username already exists' });
}
if ( await User.findOne({ email: req.body.email }).exec()) {
return fail(res, { message: 'This email already exists' });
}
next();
} catch (error) {
console.log(error);
fail(res, { message: 'Database internal error occured.' });
}
};
If you want them to run in the specific order that checkRolesExist is not fired unless the first passes, then do this:
app.post(
'/api/auth/signup',
verifySignUp.checkDuplicateUsernameOrEmail,
verifySignUp.checkRolesExist,
controller.signup
);
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));
As vague as the question seems, I need a way to send a json object and also authenticate with passport at the same time. The object is req.isAuthenticated which will be picked up with axios later in the frontend as a checkpoint. That's what I intend. So far with the code below, the object will not be sent.
app.get('/login',
passport.authenticate('saml', {
successRedirect: '/assert',
failureRedirect: '/',
}),
(req, res) => {
res.json({isAuthenticated: req.isAuthenticated()})
}
);
Here is example sample from my project:
authorizeLocal: (req, res, next) => {
passport.authenticate('local-auth', (err, user, info) => {
if (info) console.log(info);
if (err) return next(err);
if (!user) return res.status(200).send({failReason: 'wrong login/password'});
req.logIn(user, err => {
if (err) return next(err);
delete user.password;
req.session.cookie.maxAge = 24 * 60 * 60 * 1000; // 24 hours
if (user.role === 'operator') {
user.status = 'Online';
operatorsService.setStatus('Online', user.id)
.then(result => {
dialogsService.getWaitingDialogs();
user.work_time = result;
res.status(200).send(user);
})
.catch(() => res.status(200).send({failReason: 'Service error'}));
} else res.status(200).send(user);
});
})(req, res, next);
},
There you can see passport req.logIn, which (needs local-auth strategy or tother in your case) performs auth and if success fires callback logic. Deeper you can have any user/object get/generation logic. I left my case for example. OperatorsService.setStatus returns some time data, which is stored to user (user is got as callback param after strategy logic run) end sent as response. You can add user.isAuthenticated = req.isAuthenticated(); there.
So you'll have smth like:
auth.route.js
app.get('/login', authCtrl.authorizeLocal);
authCtrl.js
authorizeLocal: (req, res, next) => {
passport.authenticate('saml', (err, user, info) => {
if (info) console.log(info);
if (err) return next(err);
// if (!user) return res.status(200).send({failReason: 'wrong login/password'});
req.logIn(user, err => {
if (err) return next(err);
res.status(200).send({isAuthenticated: req.isAuthenticated()}));
});
})(req, res, next);
},
isAuthenticated() {
return compose()
// Validate jwt
.use(function(req, res, next) {
// allow access_token to be passed through query parameter as well
if (req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = 'Bearer ' + req.query.access_token;
}
validateJwt(req, res, next);
})
// Attach user to request
.use(function(req, res, next) {
User.findById(req.user._id)
.exec()
.then(user => {
if (!user) {
return res.status(401).end();
}
req.user = user;
return next();
})
.catch(err => { console.log(err); next(err) });
});
}
I am writing a piece of composable middle ware and the .then for the User.findById throws the following error Warning: a promise was created in a handler but was not returned from it. why does this happen? I return the next(); and if the user is not found i also return.