There's single api application like this:
const express = require('express')
const app = express()
const router = require('express').Router()
...
route.post('/dogs', (req, res, next) => {
const dog = new Dog() // it defined in real app
dog.validate() // error happens here
.then(() => {
return res.status(201)
})
// [1]
})
...
app.use('/api/v1', router)
app.use(notFoundErrorHandler)
app.use(globalErrorHandler)
function notFoundErrorHandler (req, res, next) {
res.status(404)
res.send({error: 'Not found'})
}
function globalErrorHandler (err, req, res, next) {
if (err) {
res.status(err.status || 500)
res.json({error: err.message || err})
}
}
If there's validation error it won't pass to the globalErrorHandler, but catching and rethrowing error solves the problem [1]:
.catch(err => { return next(err) })
Does this behaviour is normal for mongoose with not complete Promise implimentation, or it could be implimentated in another way?
It doesn't raise error when using other methods like save, find, etc.
Thats normal, yes and has nothing to do with mongoose but with Express.
Express doesn't handle unhanded exceptions implicit, you will have to handle them explicit. So in each route you will have to catch any errors and pass them to next.
I know that this may be frustrating sometimes so I would suggest that you create a route manager of sorts that will register each route with an action handler and a try/catch pattern.
There are many examples out there, here is one simple I like.
var dogHandler = require('dogHandler')
var serverRoutes = [{
path: "dogs",
method: "post",
action: dogHandler
}]
serverRoutes.forEach(route => {
app[route.method](route.path, (request, response, next) => {
route.action(request, response)
.then(() => next)
.catch(err => next(err));
});
});
Related
Im writing my expressJs application, and Im finding in my routes controller the same duplicated code for catching exception, I was wondering how to avoid this.
I have checked this thread, but I get this error "Cannot read property 'catch' of undefined" : Express Try and Catch in Form of middleware
this is my route.js
const express = require("express");
const createHttpError = require("http-errors");
const Validator = require("../middlewares/Validator");
const TaskNotFoundException = require("../services/TaskNotFoundException");
const TaskService = require("../services/TaskService");
router.get("/tasks", async (req, res, next) => {
try {
const data = await TaskService.getTasks();
res.send({ code: 200, message: "Success", data });
} catch (error) {
next(createHttpError(500));
}
});
router.get("/task/:id", async (req, res, next) => {
const { id } = req.params;
try {
const data = await TaskService.getTask(id);
res.send({ code: 200, message: "Success", data });
} catch (error) {
if (error instanceof TaskNotFoundException) {
next(createHttpError(404));
} else {
next(createHttpError(500));
}
}
});
and the list goes on
as you see in all my routes I have a try catch block with the possible errors (either only a 500, or a 500/404). And I would like to avoid this repetition.
this is my app.js
const express = require("express");
const bodyParser = require("body-parser");
const createHttpError = require("http-errors");
const api = require("./routes/api");
const app = express();
app.use(express.json());
app.use(bodyParser.json());
app.use("/api", api);
// Catch HTTP 404
app.use((req, res, next) => {
next(createHttpError(404));
});
// Error Handler
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({
error: {
status: err.status || 500,
message: err.message,
},
});
});
module.exports = app;
Like I said, it works perfectly now, I would just like to try to avoid the try catch code duplication, and Ive checked the other questions in Stackoverflow but havent helped. The solution ive linked returns a 500 with this catch undefined message (which is not what I want) and on other routes that also have a 404 it just doesnt work.
Thanks a lot!
Update:
I followed Heikos advice but still not working
api.js
const express = require("express");
const createHttpError = require("http-errors");
const Validator = require("../middlewares/Validator");
const TaskNotFoundException = require("../services/TaskNotFoundException");
const TaskService = require("../services/TaskService");
const router = express.Router();
router.get("/tasks", async (req, res, next) => {
const data = await TaskService.getTasks();
res.send({ code: 200, message: "Success", data });
});
app.js
const express = require("express");
const bodyParser = require("body-parser");
const createHttpError = require("http-errors");
const api = require("./routes/api");
const app = express();
app.use(express.json());
app.use(bodyParser.json());
app.use("/api", api);
function catchAsyncErrors(middleware) {
return async function(req, res, next) {
try {
await middleware(req, res, next);
} catch(err) {
next(err);
}
};
}
// Catch HTTP 404
app.use(catchAsyncErrors((req, res, next) => {
next(createHttpError(404));
}));
// Error Handler
app.use(catchAsyncErrors((err, req, res, next) => {
res.status(err.status || 500);
res.json({
error: {
status: err.status || 500,
message: err.message,
},
});
}));
module.exports = app;
If the code inside your async middleware functions contains an await, you must also wrap it in a try-catch block, otherwise a rejected promise will be unhandled. For example:
app.use(async function(req, res, next) {
try {
await Promise.reject("error");
} catch(err) {
next(err);
}
});
propagates the error to the error handler, but without the try-catch block it leads to an "UnhandledPromiseRejection".
You can save some typing if you wrap your middleware into a catchAsyncErrors function:
function catchAsyncErrors(middleware) {
return async function(req, res, next) {
try {
await middleware(req, res, next);
} catch(err) {
next(err);
}
};
}
router.get("/tasks", catchAsyncErrors(async (req, res, next) => {
const data = await TaskService.getTasks();
res.send({ code: 200, message: "Success", data });
}));
My function in the controller:
getWeather: (req, res) => {
const userId = req.params.userId;
weather.save().then(() => {
console.log('weather saved')
}).catch(error => { return res.status(500).json({ error }) })
}
The middleware in the model, here I want to get the userId as a param
weatherSchema.pre('save', function (req, res, next) {
console.log( req + ' pre!! '); //req
next();
})
I don't succeed, I tried to look for similar questions but their answers did not help me. What can I try next?
I guess you're confused between express middleware and mongoose middleware. The save mongoose middleware that you are using is a document middleware and it only gets a single parameter i.e. next and it is triggered before .save().
I guess an express middleware would solve your problem.
app.get("/someroute", (req, res, next) => {
// Here you have access to req.params.userId
next();
}, (req, res) => {
// Here you can save it to the db
})
I am currently trying to create an API handler that will ensure that all my requests follow the same template using express in Node JS. So I have defined the structure of the response and registered it as middleware in my express application. I am however unable to get the responses to work.
I have tried importing express into the external class for it to use the res parameter for the middleware. I have also tried it without. I have tried expressing it with middleware parameters such as (req, res, next) which didn't work either. So I am unsure what to try next.
The external handler is as follows:
exports.success = (message, results, statusCode) => {
return {
message,
error: false,
code: statusCode,
results
};
};
I however tried the following as well which didn't work:
exports.success = (message, results, statusCode) => {
return res.json({
message,
error: false,
code: statusCode,
results
});
};
I tried this as well which didn't work:
exports.success = (message, results, statusCode) => {
return (req, res, next) => {
res.json({
message,
error: false,
code: statusCode,
results
});
next();
}
};
I have implemented it in the middleware as follows:
this.app.use((req, res, next) => {
res.success = responseHandler.success;
next();
});
Is it not possible to implement what I am trying (which I doubt)? I have a feeling I am just returning the wrong thing but I am not sure what it is.
I think you should be able to get this working, I've tested a simple version of what you're trying to achieve.
I've used a bind() call to ensure the context is correct for the success() call, also we'll use the function declaration rather than an arrow function in the response-handler module for the same reason.
index.js
const express = require("express");
const port = 3000;
const app = express();
const bodyParser = require('body-parser')
const responseHandler = require('./response-handler.js')
app.use(bodyParser.json());
app.use((req, res, next) => {
res.success = responseHandler.success.bind(res);
next();
});
app.get('/success', function(req,res,error) {
res.success("Success - yay!!", { foo: 'bar' }, 418);
})
app.listen(port);
console.log(`Serving at http://localhost:${port}`);
response-handler.js
exports.success = function(message, results, statusCode) {
this.json({
message,
error: false,
code: statusCode,
results
});
};
So my routes are ordered like shown bellow
verifyToken middleware is being used in a lot of routes.
Generally if an error occurs i want the global error handler of index.js to handle it.
But if the error occurred while verifyToken middleware is being used by the login.html route with method = get i would like
to handle it inside routers/user.js which i thought i could do by using router.get(/\/login(\.html)?$/, (error, req, res, next) => {} but the error bypasses it and moves to global error handler.
index.js
const userRouter = require('./routers/user')
app.get('', (req, res) => {
res.render('index')
})
app.use(userRouter)
app.use((req, res, next) => {
res.status(404).redirect('/404.html');
})
//Global error handling
app.use( (error, req, res, next) => {
switch(error.name) {
case "UnauthorizedError":
console.log("UnauthorizedError = ", error.message)
res.status(401).redirect('/401.html');
break
case "InternalServerError":
console.log("InternalServerError = ", error.message)
res.status(500).send('whatever')
break
default:
console.log("Another error = ", error)
}
})
/routers/user.js
const verifyToken = require('../middleware/authentication/verifyToken')
router.get(/\/login(\.html)?$/, verifyToken, (req, res) => {
// If he is already logged in redirect him to dashboard
// This route works as expected
res.redirect('/admin/dashboard.html')
});
router.get(/\/login(\.html)?$/, (error, req, res, next) => {
// If error = Unauthorized
// which means that he is not logged in proceed
if(error.name === 'UnauthorizedError') res.render('login')
// else pass error to global error handler (at index.js)
else next(error)
});
module.exports = router
/middleware/authentication/verifyToken.js
const jwt = require('jsonwebtoken')
var createError = require('http-errors')
const verifyToken = async (req, res, next) => {
try {
// Do some stuff
if (token_doesnt_exist) return next(createError(401, 'TOKEN NOT FOUND', {expose: false}))
// Do some stuff
next()
} catch {
next(createError(e.status, e.message, {expose: false}))
}
})
module.exports = verifyToken
UPDATE
I ended up transforming
router.get(/\/login(\.html)?$/, (error, req, res, next) => {}
to
router.use((error, req, res, next) => {}
which i guess works since it only catches errors from the above route.
I'm not sure if this is the best way i'd really like to see an alternative.
const verifyToken = require('../middleware/authentication/verifyToken')
router.get(/\/login(\.html)?$/, verifyToken, (req, res) => {
// If he is already logged in redirect him to dashboard
// This route works as expected
res.redirect('/admin/dashboard.html')
});
router.use((error, req, res, next) => {
// If error = Unauthorized
// which means that he is not logged in proceed
if(error.name === 'UnauthorizedError') res.render('login')
// else pass error to global error handler (at index.js)
else next(error)
});
module.exports = router
Since you want to catch errors ONLY in that route you can catch the error in the middleware itself:
const verifyToken = async (req, res, next) => {
try {
//middleware logic
} catch(err) {
//error handling logic
}
Maybe not SUPER elegant but it works..
I tried to use middleware errorhandler, but doesn't work, even i set process.env.NODE_ENV ='development'
below is the server code:
var express = require('express');
var app = express();
var errorhandler = require('errorhandler');
var notifier = require('node-notifier');
process.env.NODE_ENV = 'development'; //just purposely do this, see if it can work
if (process.env.NODE_ENV == 'developmet') {
app.use(errorhandler({ log: errorNotification }));
}
function errorNotification(err, str, req) {
var title = 'Error in' + req.method + '' + req.url;
notifier.notify({
title: title,
message: str
});
}
app.get('/', function (req, res, next) {
nonexist(); //the error is still captured Native node.js not errorhandler
res.send('this is home page!');
next();
});
app.listen(1338);
no matter what kind of options i tried in errorhandler, it still doesn't work.
can anyone help me to check any setting is wrong?
Error handling is declared after all your other routes. Express moves through routes from top to bottom or left to right (if you imagine all your code on one line).
You can capitalize on this by putting a splat route after all your other routes, and it will be activated if no other exact route matches. This is how you can do an error 404 page.
It's why you build your routes like this (which will prepare you for React Router 'switch component', if you move into React coding):
GET /test/:slug
GET /test
GET /
Here is an example of a splat route and following that, your error handler middleware:
// Try switching the order of these first two
app.get('/', async (req, res, next) => {
return res.status(200).send('test')
})
app.get('*', async (req, res, next) => {
return res.status(404).send('error 404') // res.render('error/404')
})
// ERRORS
app.use(async (err, req, res, next) => {
// if next() is called with a parameter, which can be anything,
// this middleware will fire
res.status(500).send('error 500') // res.render('error/500')
throw err
})
// Try replacing your default route with this now
app.get('/', async (req, res, next) => {
return next('Extreme detonations')
})
You do not need the async functions as I have shown here, but I do it as a matter of convention so I can always slap await in there. I use explicit returns to prevent any issues with sending headers after they are already sent, and because async functions return promises, so explicit return will resolve them.
All my routes generally look like this:
app.get('/admin', async (req, res, next) => {
try {
if (!req.user) throw 'garbageUser'
const poop = await something()
return res.render('template', {
data: obj,
bonusData
})
} catch (e) {
if (e === 'garbageUser') {
log.add(`illegal: ${req.originalUrl} from ${sniffer.getClientIp(req)}`)
return res.render('403')
}
return next(e)
}
})
This should hopefully be informative for you and offer some code to forensically analyze. The Express error handler middleware takes a 4th parameter in the first position called err which contains the value passed into next().
Check out the Express documentation again after studying this, and it will make much more sense :)
To answer your question:
var express = require('express');
var app = express();
// You can add these back now that you understand
// var errorhandler = require('errorhandler');
// var notifier = require('node-notifier');
function handleErrors(error) {
console.log('I'm telling your mom about this: ' + error);
}
app.get('/', function(req, res, next) {
return next('REALLY BAD');
return res.send('this is home page!');
});
// Remember, this must be after all your other routes
app.use(function(err, req, res, next) {
console.log('Problem occurred, we could put logic here...');
console.log('Error was: ' + err);
if (err === 'REALLY BAD') {
handleErrors(err);
}
next();
});
app.listen(1338);
Try commenting this out now return next('REALLY BAD'); and run it again. You should see "this is home page!".
When you call next() with no parameter in it, Express treats it as no error. If you pass any value in, like next(err) or next('Chicken tastes good'), you will see err defined with that value in your error handling middleware.