Express.js ignore SyntaxError in case of unknown route - node.js

In the express app below:
Executing a POST for /push with invalid json data will cause an HTTP 400 error to be sent back to the client
Executing a POST with valid json data, but for an unknown route will cause an HTTP 404 error to be sent back to the client
Executing a POST with invalid json data for an unknown route, will cause an HTTP 400 error to be sent back to the client
What I'd like to achieve, is that in the third scenario, also the 404 would be sent. In other words, in case the route is unknown, I want that to be catched by app.post('*',(req, res, next)), which will throw the 404, and not by the generic middleware error handler, which throws a 400 json SyntaxError
Any way to achieve this?
const express = require('express');
const { handleError, ErrorHandler } = require('./helpers/error');
const app = express();
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.get('/', function(req, res){
res.send('Hello World');
});
app.post('/', function(req, res){
console.log(req.body.data);
res.sendStatus(200);
});
app.post('/push', function(req, res){
console.log('push API');
res.sendStatus(200);
});
app.post('*',(req, res, next) => {
console.log('Unknown route');
throw new ErrorHandler(404, 'API endpoint does not exist.');
});
app.use((err, req, res, next) => {
console.log('Middleware error handling');
handleError(err, res);
});
app.listen(3000, function(){
console.log('Server started on port 3000')
});
error.js content:
class ErrorHandler extends Error {
constructor(statusCode, message) {
super();
this.statusCode = statusCode;
this.message = message;
}
}
const handleError = (err, res) => {
const { statusCode, message } = err;
res.status(statusCode).json({
status: "error",
statusCode,
message
});
};
module.exports = {
ErrorHandler,
handleError
}

By sending a request to a non-existing route, none of your handlers are matched. Thus, your error handling middleware
app.use((err, req, res, next) => {
console.log('Middleware error handling');
handleError(err, res);
});
is executed directly. What you can do is to try to detect a Syntax/parsing error in your handleError function. Something like:
const handleError = (err, res) => {
let {statusCode, message} = err;
if (err.status === 400 && err.message.includes("Unexpected token")) {
statusCode = 404;
}
res.status(statusCode).json({
status: "error",
statusCode,
message
});
};
EDIT:
The issue can be fixed by only using the json-parser middleware for the routes you want to parse json and removing it as a general middleware for all requests:
...
//app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.post('/', express.json(), function (req, res) {
console.log(req.body.data);
res.sendStatus(200);
});
app.post('/push', express.json(), function (req, res) {
console.log('push API');
res.sendStatus(200);
});

Related

Error handling problem with Nodejs and Express

I'm trying to handle errors produced in a Nodejs with Express app. I'm following Express error-handling guide but the errors didn't catched, and nothing appears on console and browser simply show Cannot GET /abc when request the incorrect url http://localhost:3000/abc
const express = require("express")
const app = express()
const port = 3000
const config = require("./config.js");
function logErrors (err, req, res, next) {
console.error(err.stack)
next(err)
}
function errorHandler (err, req, res, next) {
res.status(500)
res.render("error", { error: err })
}
app.all("/", function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
next();
});
app.get("/favicon.ico", (req, res) => res.status(204));
app.use(express.static("public"))
app.use("/zips/:idReq", express.static("zips"))
const generateZip = require("./generateZip")
const fmeForm = require("./fmeForm")
app.get("/", function (req, res) {
res.send("Hello World !")
})
app.use("/generateZip", generateZip)
app.use("/downloadForm", fmeForm)
app.use(logErrors)
app.use(errorHandler)
app.listen(port, () => console.log(`${config.nameServer} App listening on port ${port}`))
Any idea?
You need to include a common route handler to invoke error handling method at last
app.all('*', function (req, res, next) {
// Either invoke error handler method by raising new error
next(new Error('page not found'));
// Or simply return the response
res.status(404).send('page not found')
})

Express make try/catch block into middleware to avoid duplication

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 });
}));

What are different between next(error) and throw new Error in Express framework?

Can someone explain to me about the different between two ways exception error handling in code Express JS below:
const express = require('express');
const app = express();
app.get('/test', (req, res, next) => {
// the first way:
throw new Error('my error message');
// the second way:
next(new Error('my error message'));
});
app.use((err, req, res, next) => {
res.status(err.status || 500).send(err.message || 'Internal Server Error');
});
app.listen(3000, () => console.log('Welcome to ExpressJS'));
It returns the same result handled by error middleware but what is the difference here?
Nothing, based on the source code.
try {
fn(req, res, next);
} catch (err) {
next(err);
}

How to catch error of specific router request before it ends up in global error handler

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..

How can I get Express.js to 404 only on missing routes?

At the moment I have the following which sits below all my other routes:
app.get('*', function(req, res){
console.log('404ing');
res.render('404');
});
And according to the logs, it is being fired even when the route is being matched above. How can I get it to only fire when nothing is matched?
You just need to put it at the end of all route.
Take a look at the second example of Passing Route Control:
var express = require('express')
, app = express.createServer();
var users = [{ name: 'tj' }];
app.all('/user/:id/:op?', function(req, res, next){
req.user = users[req.params.id];
if (req.user) {
next();
} else {
next(new Error('cannot find user ' + req.params.id));
}
});
app.get('/user/:id', function(req, res){
res.send('viewing ' + req.user.name);
});
app.get('/user/:id/edit', function(req, res){
res.send('editing ' + req.user.name);
});
app.put('/user/:id', function(req, res){
res.send('updating ' + req.user.name);
});
app.get('*', function(req, res){
res.send('what???', 404);
});
app.listen(3000);
Alternatively you can do nothing because all route which does not match will produce a 404. Then you can use this code to display the right template:
app.error(function(err, req, res, next){
if (err instanceof NotFound) {
res.render('404.jade');
} else {
next(err);
}
});
It's documented in Error Handling.
I bet your browser is following up with a request for the favicon. That is why you are seeing the 404 in your logs after the 200 success for the requested page.
Setup a favicon route.
You can this at the end of all routes,
const express = require('express');
const app = express();
const port = 8080;
// All your routes and middleware here.....
app.use((req, res, next) => {
res.status(404).json({
message: 'Ohh you are lost, read the API documentation to find your way back home :)'
})
})
// Init the server here,
app.listen( port, () => {
console.log('Sever is up')
})
Hope it helpful, I used this code in bottom of routes
router.use((req, res, next) => {
next({
status: 404,
message: 'Not Found',
});
});
router.use((err, req, res, next) => {
if (err.status === 404) {
return res.status(400).render('404');
}
if (err.status === 500) {
return res.status(500).render('500');
}
next();
});
You can use this
const express = require('express');
const app=express();
app.set('view engine', 'pug');
app.get('/', (req,res,next)=>{
res.render('home');
});
app.use( (req,res,next)=>{
res.render('404');
})
app.listen(3000);
I wanted a catch all that would render my 404 page only on missing routes and found it here in the error handling docs https://expressjs.com/en/guide/error-handling.html
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(404).render('404.ejs')
})
This worked for me.
Very simple you can add this middleware.
app.use(function (req, res, next) {
//Capture All 404 errors
res.status(404).render("404.ejs")
})
404 error in a service is typically used to denote that the requested resource is not available. In this article we will see how to handle 404 error in express.
We need to handle the Error and Not-Found collectively as
Write two separate middleware for each,
// Import necessary modules
const express = require('express');
// Create a new Express app
const app = express();
// Define routes and middleware functions
app.get('/', (req, res) => {
res.send('Hello World!');
});
// Catch 404 Not Found errors and forward to error handler
app.use((req, res, next) => {
const error = new Error('Not Found');
error.status = 404;
next(error);
});
// Error handler middleware function
app.use((err, req, res, next) => {
// Set status code and error message based on error object
res.status(err.status || 500);
res.send({
error: {
message: err.message
}
});
});
// Start the server
app.listen(3000, () => {
console.log('Server started on port 3000');
});

Resources