MongoDB/Mongoose Security for a MERN Stack [duplicate] - node.js

I have set up a web application with some internal pages requiring a login. I used Node with Express.js to set up the server and controlling the routes and authentication works fine.
I came up to a #zanko suggestion in a question related to the same application to avoid the replication of the authentication code in the route of every page, like is now.
At the moment my app.js looks like this (the following is an excerpt):
var session = require('express-session');
//use sessions for tracking logins
app.use(session({
secret: 'mercuia',
resave: true,
saveUninitialized: false,
store: new MongoStore({
mongooseConnection: db
})
}));
// serve static files from template
app.use(express.static(__dirname + '/public'));
// include routes
var routes = require('./routes/router');
app.use('/', routes);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
var err = new Error('File Not Found');
err.status = 404;
next(err);
});
// error handler
// define as the last app.use callback
app.use(function (err, req, res, next) {
res.status(err.status || 500);
res.send(err.message);
});
and my authentication method (in routes.js) looks like this (in the example, for the route /clientPage):
// GET route after registering
router.get('/clientPage', function (req, res, next) {
User.findById(req.session.userId)
.exec(function (error, user) {
if (error) {
return next(error);
} else {
if (user === null) {
var err = new Error('Not authorized! Go back!');
err.status = 400;
return next(err);
} else {
return res.sendFile(path.join(__dirname + '/../views/clientPage.html'));
}
}
});
});
How can I write an authentication middleware instead (using the same logic) and call it for all and only the requiring routes?

You can create a new module called auth.js and then use it to check if users are authorized or not:
auth.js
module.exports.isAuthorized = function(req, res, next) {
User.findById(req.session.userId).exec(function (error, user) {
if (error) {
return next(error);
} else {
if (user === null) {
var err = new Error('Not authorized! Go back!');
err.status = 401;
return next(err);
} else {
return next();
}
}
});
}
routes.js
var auth = require('./auth');
// GET route after registering
router.get('/clientPage', auth.isAuthorized, function (req, res, next) {
res.sendFile(path.join(__dirname + '/../views/clientPage.html'));
});

Create a module (a file that exports a function, in this case a middleware function). A middleware function has following signature function (req, res, next) { .... }
restrict.js
module.exports = function (req, res, next) {
User.findById(req.session.userId)
.exec(function (error, user) {
if (error) {
return next(error);
} else {
if (user === null) {
const err = new Error("Not authorized! Go back!");
err.status = 400;
return next(err); // This will be caught by error handler
} else {
return next(); // No error proceed to next middleware
}
}
});
};
app.js
// serve static files from template
app.use(express.static(__dirname + '/public'));
// include routes
const routes = require('./routes/router');
//If you have a more granular route you can split it
const someOtherRoute = require('./routes/someotherRoute');
const restrictMiddleware = require("./restrict");
app.use("/", someOtherRoute); // this route will not be check for authorization
app.use(restrictMiddleware);
app.use('/', routes);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
const err = new Error('File Not Found');
err.status = 404;
next(err);
});
// error handler
// define as the last app.use callback
app.use(function (err, req, res, next) {
res.status(err.status || 500);
res.send(err.message);
});
I would use const and let if your environment support it. Its 2017 :)

Related

Exposing user object inside the req object

I am trying to get user object inside the req, so I can have it on all my routes. This is my setup:
app.js:
// Use the passport middleware
app.use(passport.initialize());
// load passport strategies
const localSignupStrategy = require('./server/passport/local-signup');
const localLoginStrategy = require('./server/passport/local-login');
passport.use('local-signup', localSignupStrategy);
passport.use('local-login', localLoginStrategy);
// View engine setup
app.set('views', path.join(__dirname, '/server/views'));
app.set('view engine', 'pug');
// Serve static assets normally
app.use(express.static(path.join(__dirname, '/dist')));
// Define routes
app.use('/auth', auth); //Auth controller
app.use('/api', api);
Route for Auth controller:
const express = require('express');
const router = express.Router();
const authController = require('../main/controllers/authController');
// POST /auth/signup
router.post('/signup', authController.postSignup);
// POST /auth/login
router.post('/login', authController.postLogin);
module.exports = router;
authController.postLogin
exports.postLogin = function(req, res, next) {
const validationResult = validateLoginForm(req.body);
if (!validationResult.success) {
return res.status(400).json({
success: false,
message: validationResult.message,
errors: validationResult.errors
});
}
return passport.authenticate('local-login', (err, token, userData) => {
if (err) {
if (err.name === 'IncorrectCredentialsError') {
return res.status(400).json({
success: false,
message: err.message
});
}
return res.status(400).json({
success: false,
message: 'Could not process the form.'
});
}
return res.json({
success: true,
message: 'Login success.',
token,
user: userData
});
})(req, res, next);
};
This is my normal controller route:
// GET /api/cms
router.get('/cms/', authCheck(), getCmsDataController.getCmsData);
module.exports = router;
authcheck.js
module.exports = function(roles) {
// Return middleware
return (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).end();
}
// Get the last part from a authorization header string like "bearer token-value"
const token = req.headers.authorization.split(' ')[1];
// Decode the token using a secret key-phrase
return jwt.verify(token, config.jwtSecret, (err, decoded) => {
// 401 not unauthorized
if (err) return res.status(401).end();
const userId = decoded.sub;
// Check if user exists
return User.findById(userId, (err2, user) => {
if (err2 || !user) return res.status(401).end();
req.currentLoggedUser = user;
console.log(user.role);
if (roles) {
if (roles.indexOf(user.role) > -1) return next();
else return res.status(401).end();
}
return next();
});
});
};
};
And the controller itself:
// GET /api/cms-data/
exports.getCmsData = function(req, res, next) {
return res.json({
message: 'Lets see does this thing work or not!!!'
});
};
Issue is when I reach the getCmsData controller, I would like to have a user object inside the req object. My user has some properties like role and gender, which I need access to. I have one hacky solution, but I think there is a way to do that.
Could you create a middleware function for this purpose:
function getRequestUser(req) {
// In reality you'd load from data store based on request.
return {id: 1, name: "Jim Smith"};
}
function addUserMiddleWare(req, res, next) {
req.user = getRequestUser(req);
next();
}
// Then add it to your route.
// GET /api/cms
router.get('/cms/', authCheck(), addUserMiddleWare, getCmsDataController.getCmsData);
module.exports = router;
// Or, apply to all paths on router
router.use(addUserMiddleWare);

NodeJS app using CSRF for web and JWT for API does async.parallel out of order

When a logged-in user gets to a page through the browser using EJS I'm able to get the function to do what it's supposed to but when I use the API with Ionic using a logged in user with JWT, the async.parallel function doesn't "wait" to do things in order.
Here is my function:
console.log('1');
async.parallel([
function(callback){
buildAlertButtonsArray.getRealTestAlerts(req,function(arrayAlerts) {
console.log('2');
callback(null, arrayAlerts);
});
},
function(callback) {
if(req.decoded) //API
callback('API');
else //EJS
functions.aclSideMenu(req, res, function (acl) {callback(null, acl);}); //aclPermissions sideMenu
}
],function(err, results){
console.log('3');
})
when I login through the browsed on my console.log() is 1, 2, 3 but when I login through the API using JWT I get 1, 3, 2.
Here is my app.js:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var app = express();
var bluebird = require('bluebird');
//me
var mongoose = require('mongoose');
var db = mongoose.connection;
var cors = require('cors');
var session = require('client-sessions');
var flash = require('express-flash');
//.js file
var routesApi = require('./routes/api');
var routesEjs = require('./routes/ejs');
var routes = require('./routes/index');
//var login = require('./routes/authentication/login');
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(cookieParser());
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.urlencoded({ extended: true })); //was FALSE by default. was TRUE for auth Template
// middleware
app.use(session({
cookieName: 'session',
secret: 'mysecret',
duration: 30 * 60 * 1000,
activeDuration: 30 * 60 * 1000,
httpOnly: true, //doesn't let javascript access cookies ever
secure: true, // only use cookies over https
ephemeral: true // delete this cookie when the browser is closed (nice when people use public computers)
}));
app.use(flash());
app.use(function(req, res, next){
res.locals.success_messages = req.flash('success_messages');
res.locals.error_messages = req.flash('error_messages');
next();
});
// use cors
app.use(cors());
app.use('/public', express.static(path.join(__dirname, 'public')));
app.use('/api', routesApi);
app.use('/', routes);
app.use('/', routesEjs);
//bluebird
mongoose.Promise = require('bluebird');
//connecting to database
mongoose.connect('mongodb://myip:2999/SMECS_database', { useMongoClient: true });
//if we connect successfully or if a connection error occurs
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function (callback) {
// yay!
});
// error handlers
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
Here is my Login function for both EJS using CSRF and API using JWT:
module.exports.postLogin = function(req, res, next) {
if (req.body.pushToken) { // run SMECS API
models.Users.findOne({
email: req.body.email.toLowerCase()
}, function (err, user) {
if (err) throw err;
if (!user) {
res.json({success: false, message: 'Authentication failed. User not found.'});
} else if (user) {
//check if password matches
if (!bcrypt.compareSync(req.body.pin, user.pin)) {
res.json({success: false, message: 'Authentication failed. Wrong password.'});
} else {
// if user is found and password is right
// create a token
var token = jwt.sign({user: user}, config.secret, {
//expiresIn: 1440 // expires in 24 hours
});
user.save(function (err) {
if (err) {
res.json({
success: false,
message: 'contact your system administrator. pushToken not saved'
});
} else {
// return the information including token as JSON
res.json({
success: true,
message: 'Welcome aboard!',
token: token,
userRoleID: user.userRoleID,
userRoleName: user.userRoleName,
userPrivilegeID: user.userPrivilegeID,
userPrivilegeName: user.userPrivilegeName,
firstName: user.firstName,
lastName: user.lastName,
email: user.email
});
}
});
}
}
});
}
else { //run SMECS EJS
models.Users.findOne({email: req.body.email.toLowerCase()}, function (err, user) {
if (!user || user.softDeleted !== null) {
//Parent Self Registration Login
models.ParentSelfRegistration.findOne({email: req.body.email.toLowerCase()}, function (err, parentSelfRegistration) {
if (!parentSelfRegistration) {
res.render('login', {error: "ERROR: Incorrect email or pin.", csrfToken: req.csrfToken()});
} else {
if (req.body.pin == parentSelfRegistration.pin) {
req.session.user = parentSelfRegistration;
res.redirect('/parentsSelfRegistration/registerParentStep1');
} else {
res.render('login', {error: "ERROR: Incorrect email or pin.", csrfToken: req.csrfToken()});
}
}
});
//END OF checks for users in UtilityUsers database
} else {
if (bcrypt.compareSync(req.body.pin, user.pin)) { // if user is found and password is right
req.session.user = user;
res.redirect('/dashboard');
//}
} else {
//res.status(400).send('Current password does not match');
res.render('login', {error: "ERROR: Incorrect email or pin.", csrfToken: req.csrfToken()});
//res.render('login', { error: "ERROR: Incorrect email or pin."});
}
}
});
}
};
Here is my ejs.js file:
//Dependencies
var express = require('express');
var routerEjs = express.Router();
var login = require('./authentication/login');
var auth = require('./authentication/auth');
var chooseAlert = require('./alerts/sendingReceiving/1.chooseAlert');
var login = require('./authentication/login');
var csrf = require('csurf');
routerEjs.use(csrf());
/* GET login page. */
routerEjs.get('/login', login.getLogin, function(req, res) {});
routerEjs.post('/login', login.postLogin, function(req, res) {});
routerEjs.get('/logout', login.getLogout, function(req, res) {});
module.exports = routerEjs;
and my api.js file:
//Dependencies
var express = require('express');
var routerApi = express.Router();
var login = require('./authentication/login');
var auth = require('./authentication/auth');
var chooseAlert = require('./alerts/sendingReceiving/1.chooseAlert');
routerApi.post('/login', login.postLogin, function(req, res) {});
routerApi.get('/chooseGroup', auth.auth, chooseAlert.showGroups, function(req, res) {});
routerApi.get('/alerts/sending/chooseAlert', auth.auth, chooseAlert.showAlerts, function(req, res) {});
/* Update pushToken ------------------------------------*/
routerApi.post('/updatePushToken', auth.auth, auth.pin, function(req, res) {});
module.exports = routerApi;
I figured out my problem. I was missing a NULL on my callback...
console.log('1');
async.parallel([
function(callback){
buildAlertButtonsArray.getRealTestAlerts(req,function(arrayAlerts) {
console.log('2');
callback(null, arrayAlerts);
});
},
function(callback) {
if(req.decoded) //API
callback(NULL, 'API');
else //EJS
functions.aclSideMenu(req, res, function (acl) {callback(null, acl);}); //aclPermissions sideMenu
}
],function(err, results){
console.log('3');
})

How to avoid internal server error without using try catch clause in node js?

Server.js file
var express = require('express');
var path = require('path');
var logger = require('morgan');
var bodyParser = require('body-parser');
var fs = require('fs');
var app = express();
var os = require('os');
//app.use(logger('dev'));
app.use(bodyParser.json());
app.all('/*', function (req, res, next) {
// CORS headers
res.header("Access-Control-Allow-Origin", "*"); // restrict it to the required domain
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
// Set custom headers for CORS
res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key');
if (req.method == 'OPTIONS') {
res.status(200).end();
} else {
next();
}
});
// Auth Middleware - This will check if the token is valid
// Only the requests that start with /api/v1/* will be checked for the token.
// Any URL's that do not follow the below pattern should be avoided unless you
// are sure that authentication is not needed
//app.all('/api/v1/*', [require('./middlewares/validateRequest')]);
app.use('/', require('./routes')); // Write url in the routes.js file
app.use(function (err, req, res, next) {
if (!err) {
return next();
}
else {
return next();
}
});
//404 error handler
// If no route is matched by now, it must be a 404
app.use(function (req, res, next) {
var err = new Error('URL Not Found');
err.status = 404;
var date = new Date();
next(err);
});
// Start the server
app.set('port', process.env.PORT || 3000);
var server = app.listen(app.get('port'), function () {
console.log('Express server listening on port ' + server.address().port);
});
I have read to handle internal server error
Do like that. I even tried that too but it won't run as I wanted
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
console.log(err.message+' w');
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
How to avoid that so that the server won't shot down ?
Tried that link too https://expressjs.com/en/guide/error-handling.html , http://code.runnable.com/UTlPPF-f2W1TAAEU/error-handling-with-express-for-node-js
For express middleware error handling
Catch 404 and forward to error handler
app.use(function(req, res) {
res.status(404).send({
error: 'Not found'
});
});
if (app.get('env') === 'development') {
app.use(function(error, req, res, next) {
debug('http_status: %d, %s', error.status || 500, error.message);
next(error);
});
}
app.use(function(error, req, res) {
res.status(error.status || 500).send({
error: 'Internal server error'
});
});
Catch the uncaught errors that weren't wrapped in a domain or try catch statement
Note: Do not use this in modules, but only in applications, as otherwise, we could have multiple of these bound
process.on('exit', function(code) {
});
process.on('uncaughtException', function(err) {
console.log(err)
});
process.on('SIGINT', function() {
process.exit(0);
});

Authentication failure when using express-session

I wrote some code for login authentication using express. I used express-session. Code sample is
// Authentication and Authorization Middleware
var auth = function(req, res, next) {
if (req.session && req.session.admin) {
return next();
} else {
console.log("failed");
return res.sendStatus(401);
}
}
// Login endpoint
router.post('/login', function (req, res) {
var collection = db.get("login");
collection.find({}, function(err, details) {
if (!req.body.username || !req.body.password) {
res.send('login failed');
} else if(req.body.username === details[0].name && req.body.password === details[0].password ) {
req.session.admin = true;
var data = {
"status": "success",
"message": "login success!"
}
res.send(data);
} else {
var data = {
"status": "failure",
"message": "login failed"
}
res.send(data);
}
});
});
// Logout endpoint
router.get('/logout', auth, function (req, res) {
req.session.destroy();
res.send("logout success!");
});
//Getting Details endpoint
router.get("/data", auth, function(req, res) {
var collection = db.get('details');
collection.find({}, function(err, details){
if (err) throw err;
res.json(details);
});
});
After successful login req.session.admin is set to true. But, at Authentication middleware (auth), it is sending 401 status. Please help me solve this problem.
code:
//app.js
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
var getDetails = require('./routes/getDetails');
var app = express();
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', '*');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
// view engine setup
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.cookieParser());
app.use(express.static(path.join(__dirname, 'routes')));
app.use(express.session({
secret: '2C44-4D44-WppQ38S',
resave: true,
saveUninitialized: true
}));
app.use('/getDetails',getDetails);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
//app.listen(3001);
module.exports = app;
//getDetails.js
var express = require('express');
var router = express.Router();
var monk = require('monk');
var db = monk('localhost:27017/saidb');
// Login endpoint
router.post('/login', function (req, res) {
var collection = db.get("login");
//var data;
collection.find({}, function(err, details) {
//res.json(details);
if (!req.body.username || !req.body.password) {
res.send('login failed');
} else if(req.body.username === details[0].name && req.body.password === details[0].password ) {
req.session.admin = true;
var data = {
"status": "success",
"message": "login success!"
}
res.send(data);
} else {
var data = {
"status": "failure",
"message": "login failed"
}
res.send(data);
}
});
});
var auth = function(req, res, next) {
if (req.session && req.session.admin) {
console.log("success");
return next();
} else {
console.log("failed");
return res.sendStatus(401);
}
}
// Logout endpoint
router.get('/logout', auth, function (req, res) {
req.session.destroy();
res.send("logout success!");
});
//Getting Details endpoint
router.get("/data", auth, function(req, res) {
var collection = db.get('details');
collection.find({}, function(err, details){
if (err) throw err;
res.json(details);
});
});
//Get details by ID endpoint
router.get("/data:id", auth, function(req, res) {
var collection = db.get('details');
collection.find({id: parseInt(req.params.id)}, function(err, details){
if (err) throw err;
res.json(details);
});
});
//Adding Details endpoint
router.post("/data", auth, function(req, res) {
var collection = db.get("details");
collection.count({id : parseInt(req.body.id)},function(err,count){
if(!err){
if(count>0){
//send the response that its duplicate.
//console.log(errorororrrroror);
res.send("r");
}
}
});
console.log("request", req.body);
collection.insert({ id: parseInt(req.body.id),
website: req.body.website,
subtitle: req.body.subtitle,
url: req.body.url },
function(err, details) {
if(err) throw err;
res.json(details);
})
});
//Editing Details endpoint
router.put("/data", auth, function(req,res){
var collection = db.get("details");
collection.update({id: parseInt(req.body.id)},
{id: parseInt(req.body.id), website: req.body.website, subtitle: req.body.subtitle, url: req.body.url},
function(err, details){
if(err) throw err;
res.json(details);
})
});
//Deleting details endpoint
router.delete("/data", auth, function(req,res){
var collection = db.get("details");
collection.remove({id: parseInt(req.body.id)}, function(err, details){
if(err) throw err;
res.json(details);
})
});
module.exports = router;
Use these lines in your server file at top after express object like this
var app = express();
app.use(express.cookieParser());
app.use(express.session({secret: "sdsddsd23232323" }));

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