I have the following code:
app.post('/routes/passwordReset/:userId', async (req, res, next) => {
var userId = req.params.userId
let record = (await vars.connection.queryP('SELECT * FROM contacts WHERE id = ?', userId))[0]
if (!record) {
res.status(404).send('')
return
}
// Calculate token and expiry, update database
var token = require('crypto').randomBytes(48).toString('base64').replace(/[^a-zA-Z0-9]/g, '').substr(0, 28)
var now = new Date()
var inOneHour = new Date(now.getTime() + (1 * 1000 * 60 * 60))
await vars.connection.queryP('UPDATE contacts SET recoverToken = ?, recoverTokenExpiry = ? WHERE id = ?', [ token, inOneHour, userId ])
res.status(200).send('')
})
If I create an artificial error (e.g. the field for recoverTokenExpiry is artificially too short) I end up with:
[[12:19:55.649]] [ERROR] (node:11919) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): Error: ER_DATA_TOO_LONG: Data too long for column 'recoverToken' at row 1
I somehow thought that Express would have try/catch around middleware and called next(err) if an error was thrown. Maybe not so?
So, should I wrap each route with try/catch(e), doing next(e) if there is an error?
Express does not do anything for you. It is very bare bones.
You have two options:
try-catch
Calling next() will pass the req and res to the next middleware in the stack.
Calling next(err), in other giving it an Exception object, will call any error middleware in the stack.
Define your error handlers in its own file:
// error-middleware.js
/**
* Handler to catch `async` operation errors.
* Reduces having to write `try-catch` all the time.
*/
exports.catchErrors = action => (req, res, next) => action(req, res).catch(next)
/**
* Show useful information to client in development.
*/
exports.devErrorHandler = (err, req, res, next) => {
err.stack = err.stack || ''
const status = err.status || 500
const error = { message: err.message }
res.status(status)
res.json({ status, error })
}
Then move your password reset logic out of app.post and into its own dedicated function:
// password-controller.js
exports.resetPassword = async (req, res) => {
// do work
// ...
}
This makes it easier to write unit tests for and a clear separation of concerns.
Next create your password reset route(s):
// password-reset-routes.js
const express = require('express')
const router = express.Router()
const passwordController = require('./password-controller')
const { catchErrors } = require('./error-middleware')
router.post('/routes/passwordReset/:userId', catchErrors(passwordController.resetPassword))
module.exports = router
Notice that the controller defined above is imported and used here. In addition you see that the controller action resetPassword is wrapped with catchErrors. This avoids having to write try-catch all the time and any errors will be caught by the catchErrors middleware.
Finally, wire it all up in your main app.js file:
// app.js
const express = require('express')
const { devErrorHandler } = require('./error-middleware')
const passwordResetRoutes = require('./password-reset-routes.js')
const app = express()
app.use(devErrorHandler)
app.use(passwordResetRoutes)
module.exports = app
Well, as you're using await keyword, it means the execution flow isn't asynchronous anymore, that's why it fails and yes, you must use try-catch block every time. To avoid this, you can instead follow the asynchronous flow and use then() and catch() respectively, so you can do something like:
...
.then((status)=>{
res.status(200).send('')
})
.catch((err)=>{
next(err);
});
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With