Custom middleware express.js framework ordering - node.js

I am writing custom middleware(s) to send request data(like request path, response code, request timestamp, etc) to a remote server on every request. I'm unable to figure out the ordering of my middlewares.
I have created 2 middlewares,
a) process_request (m1) --> this middleware just adds a timestamp to the request object to register when the request was received.
b) process_response (m2) --> this middleware posts the required data to the remote server
m1
function process_request(req, res, next) {
req.requestTime = Date.now()/1000;
next();
}
m2
function process_response(req, res, next) {
const request_obj = {};
request_obj.request_timestamp = req.requestTime;
request_obj.response_timestamp = Date.now()/1000;
request_obj.path = req.protocol + "://" +
req.get('host') + req.originalUrl;
request_obj.response_code = res.statusCode;
send_perf_request(request_obj); // sends remote https request not shown here
}
I can think of two ordering options in my app.js:
Order 1:
m1
route 1
route 2
...
route n
m2
404 request handler middleware
Order 2:
m1
route 1
route 2
...
route n
404 request handler middleware
m2
404 request handler middleware
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
The problem with order 1 is that I won't be able to catch 404 requests, which I want to.
The problem with order 2 in all in request the responseCode=404 as the 404 request handler middleware is doing that.
I am new to node.js & confused if I am thinking about this the right way.
My app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const custom_middleware = require("custom_middleware");
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(custom_middleware.process_request); //*calling middleware
app.use('/', indexRouter);
app.use('/users', usersRouter);
//catch 404 and forward to error handler
// app.use(function(req, res, next) {
// console.log("in 404 handler");
// //next(createError(404, "this page doesn't exist;"));
// next();
// });
// error handler
app.use(function(err, req, res, next) {
// this is called only in case of errors
// set locals, only providing error in development
console.log("in error handler");
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
console.log(res.statusCode);
//res.render('error');
next(err);
});
app.use(custom_middleware.process_exception); //*calling middleware
module.exports = app;
Custom middleware file
function process_request(req, res, next) {
// this middleware adds request timestamp
console.log("in process request middleware");
req.requestTime = Date.now()/1000;
res.on("finish", function (){
// this middleware collects performance data
console.log("in process response middleware");
const request_obj = {};
request_obj.request_timestamp = req.requestTime;
request_obj.response_timestamp = Date.now()/1000;
request_obj.ip_address = ip.address();
request_obj.path = req.protocol + "://" +
req.get('host') + req.originalUrl;
request_obj.requester = null;
request_obj.response_code = res.statusCode;
console.log(request_obj.response_code);
send_perf_request(request_obj);
})
next();
}
function process_exception(err, req, res, next) {
// this middleware collects exception data
console.log("in process exception middleware");
const error_obj = {};
error_obj.path = req.protocol + "://" +
req.hostname + req.originalUrl;
error_obj.exception = "error occured";
error_obj.traceback = err.stack;
error_obj.user = null;
error_obj.ip_address = ip.address();
send_exception_request(error_obj);
next();
}
My routes/index.js
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
throw new Error('this does not exist'); // error manually triggered
res.status(500);
});
module.exports = router;

As mentioned in the comments, abstract the middlewares to avoid defining a set of middlewares on each route.
To replace m1 Create a global middleware, which you define before all your other middlewares which sets res.on("finish", function () {}) event handler to do something when the route is done. I'm pretty sure this is the only event you will get the actual correct res.statusCode if you're doing res.status() anywhere.
Then move your 404 error handler logic into the main error handler, you can then check for status code and all that good stuff before logging and responding. You can also use req.xhr to determine how to respond.
Also, you can use a catch-all to pick up on route 404's: app.get('*', function (req, res, next) { then fire off an error which the error handler handles.
Here is an example putting it all together:
const express = require('express')
const app = express()
// logger middleware
app.use((req, res, next) => {
// set something
req.requestTime = Date.now() / 1000;
// gets fired after all routes have been handled
res.on("finish", function () {
//
req.finishTime = Date.now() / 1000;
// do something with req, res objects
console.log('[in logger] originalUrl:', req.originalUrl)
console.log('[in logger] status code:', res.statusCode)
})
next()
})
// apply routes
// ...
app.get('/', (req, res, next) => {
try {
// example: look for something, which is not found
const mock = false
if (!mock) {
let err = new Error('Mock was not found!')
err.status = 404
throw err
}
res.send('Hello World!')
} catch (err) {
next(err)
}
})
// app.get('/foo', ...
// not found route (catch all)
app.get('*', (req, res, next) => {
let err = new Error('Page not found!')
err.status = 404
next(err)
})
// error handler (will catch everything even uncaught exceptions)
app.use((error, req, res, next) => {
//
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate')
res.header('Expires', '-1')
res.header('Pragma', 'no-cache')
// set status from error
res.status(error.status || 500)
if (req.xhr) {
// is ajax call, send error as json
res.json({
error: error.name || 'Server error',
status: error.status || 500,
message: error.message || 'Internal server error'
})
} else {
// render a view instead (would need to add a view engine)
res.render('error/' + (error.status || 500), error)
}
})
app.listen(3000, function () {
console.log('App listening on port 3000!')
})

Related

created a simple middleware game with Nodejs, and an http header error occurs

I am a beginner studying Nodejs.
I have recently studied node middleware and have created a simple game using middleware.
The purpose of the generated code is to respond to hello by connecting as root and then respond to the browser with 50% probability through the middleware.
However, I get the following error:
I did a search and found that res.send is not available after next ().
Is that correct?
But I could not figure out why and I did not realize why the code did not work.
code
const express = require('express');
var app = express();
app.use('/', (req, res, next) =>{
res.send('hello');
next();
});
app.use((req, res, next) => {
if (+new Date() % 2 === 0) {
console.log('continue');
res.send('lucky!');
next();
} else {
console.log('failed');
res.send('end');
}
});
app.use((req, res, next) => {
if (+new Date() % 2 === 0) {
console.log('continue');
res.send('lucky!');
next();
} else {
console.log('failed');
res.send('end');
}
});
app.listen(3000, () => console.log(`Example!`))
error
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
const express = require('express');
var app = express();
app.use(/^\/$/, (req, res, next) =>{
res.send('hello');
return;
});
app.use((req, res, next) => {
if (+new Date() % 2 === 0) {
console.log('continue');
res.send('lucky!');
next();
} else {
console.log('failed');
res.send('end');
}
});
app.listen(3000, () => console.log(`Example!`))
you cannot use res.send() twice
res.send() = Sends the HTTP response.

Empty body in POST requests to an API in NodeJS from Postman (but not from automated tests)

In POST requests through Postman in a NodeJS API I receive empty bodies... (I receive exactly this: {}), however from automated tests it works perfectly. Actually from Postman that happends when I send it as "form" or as "raw" with "text", but if I send it as a "JSON" in raw it simply freezes in "loading..."
When I search I read about adding these 2 lines related to body parse it made it work for the tests but not for Postman:
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
The entire code is here: https://github.com/nemenosfe/TakeMe-Api
But the 3 key files are the following (simplified):
app.js:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const cors = require('cors');
const user = require('./routes/users');
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use('/users', user);
app.use(cors());
// 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);
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
});
app.listen(8888, function() {
console.log("Node server running on http://localhost:8888");
});
module.exports = app;
routes/users:
"use strict"
const express = require('express'),
router = express.Router(),
/* ... many require and other code probably not relevant for the problem ... */
router
.post('/', function(req, res, next) {
console.log(`conditions: ${(!req.body)} ${(!req.body.uid)} ${(!req.body.provider)} ${JSON.stringify(req.body)}`);
// This console.log appears as follows in the console once I make the request by Postman: false true true {}
// But it receives what it shoulds with automated tests
if (!req.body || !req.body.uid || !req.body.provider) { utilsErrors.handleNoParams(res); }
else {
/* ... There is a lot of code here but it's not relevant for the problem because it doesn't even reaches this point. */
}
})
module.exports = router
tests/users:
"use strict"
let request = require('supertest-as-promised');
const api = require('../app');
/* ... Another require not relevant for the problem ... */
request = request(api);
describe('Users route', function() {
describe.only('POST /users', function() {
it("should create a new user when it doesn't exist", function(done) {
const params = {
'appkey': helperCommon.appkey,
'uid': 1,
'provider': 'providerTest',
'name': 'fakeName'
};
request
.post('/users')
.set('Accept', 'application/json')
.send(params)
.expect(201)
.expect('Content-Type', /application\/json/)
.then((res) => {
expect(res.body).to.have.property('user');
const userResponse = res.body.user;
expect(userResponse).to.have.property('uid', params.uid);
expect(userResponse).to.have.property('provider', params.provider);
expect(userResponse).to.have.property('name', params.name);
/* ... other expectectations that are not important for the problem ... */
done();
}, done)
});
});
Thanks!
Make sure your are sending the POST request In postman as a x-www-form-urlenconded

Express 4 middleware error handler not being called

For certain pages I have custom 500, 404 and 403 error handling in my app. So for instance after an unsuccessful database query I'd go:
return next({status: 404, message: 'Record not found'});
or
return next(new Error('Bad things have happened')});
In my middleware I have an error handler:
app.use(function (err, req, res, next) {
// handle error
});
Problem is that the error handler is never called, instead the error callstack is being printed into the browser. I want the handler to render a custom error page.
app.js
var express = require('express')
, app = express()
, swig = require('swig')
, config = require('./lib/config')
, env = process.env.NODE_ENV || 'development'
, path = require('path');
config.configure(env);
app.engine('html', swig.renderFile);
app.set('view cache', false);
swig.setDefaults({
cache: config.get('swigCache')
});
app.set('view engine', 'html');
app.set('views', __dirname + '/lib/views');
require('./lib/util/swig');
require('./lib/initialisers/mongodb')();
require('./lib/initialisers/aws')();
require('./lib/middleware')(app); // first load middleware
require('./lib/routes')(app); // then routes
var server = app.listen(config.get('port'), function() {
console.info('config: ' + JSON.stringify(config.getCurrent()));
console.info('NODE_ENV: ' + env);
console.info('server running: ' + JSON.stringify(server.address()));
});
routes.js
module.exports = function(app){
app.get('/', require('./views/').index);
app.get('/blog', require('./views/blog').index);
app.get('/blog/:slug', require('./views/blog').getBySlug);
app.route('/report/:slug')
.get(require('./views/report/').index)
.post(require('./views/report/').doReport);
// Very long file with tons of routes. Simplified version.
middleware.js
var express = require('express')
, app = express()
, path = require('path')
, logger = require('morgan')
, cookieParser = require('cookie-parser')
, bodyParser = require('body-parser')
, passport = require('passport')
, session = require('express-session')
, mongoStore = require('connect-mongo')(session)
, compression = require('compression')
, favicon = require('serve-favicon')
, config = require('./config')
, flash = require('connect-flash')
, multer = require('multer')
, csrf = require('csurf');
module.exports = function(app) {
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json());
app.use(cookieParser());
app.use(csrf({ cookie: true }));
app.use(express.static(path.join(__dirname, config.get('staticContentPath')), {
maxAge: (60 * 60 * 24) * 1000
}));
app.use(session({
resave: true,
saveUninitialized: true,
secret: 'da755fc0-6882-11e4-9803-0800200c9a66',
cookie: {
maxAge: 24 * 60 * 60 * 1000 // 24 hrs
},
store: new mongoStore({
url: config.getMongoConn()
})
}));
app.use(logger('dev'));
app.use(flash());
/**
* 301 redirects
*/
app.use(function(req, res, next) {
var host = req.get('host');
// AWS IP --> http
if (host == 'xx.xxx.xxx.xxx') {
return res.redirect(301, config.get('url') + req.originalUrl);
}
// AWS origin --> http
if(host == 'xxx-xxx-xxx-xxx-xxx.ap-southeast-2.compute.amazonaws.com'){
return res.redirect(301, config.get('url') + req.originalUrl);
}
// www --> http
if (/^www\./.test(host)) {
host = host.substring(4, host.length);
return res.redirect(301, req.protocol + '://' + host + req.originalUrl);
}
// Trailing slash --> http
if (req.path.substr(-1) == '/' && req.path.length > 1) {
var query = req.url.slice(req.path.length);
return res.redirect(301, req.path.slice(0, -1) + query);
}
next();
});
// Delete expired Mongo sessions from DB
app.use(function (req, res, next) {
req.session._garbage = new Date();
req.session.touch();
next();
});
/**
* Setting Cache control header for Ajax requests to 30 minutes
*/
app.use(function (req, res, next) {
if(req.xhr){
res.header('Cache-Control', 'max-age=' + 1800 + ', public');
}
next();
});
app.use(compression());
app.use(
multer({
dest: config.get('uploads').folders.temp
})
);
app.use(passport.initialize());
app.use(passport.session());
var initPassport = require('./passport/init');
initPassport(passport);
app.use(function (req, res, next) {
res.locals = {
root : 'http://' + req.headers.host,
sitename : require('./config').get('sitename'),
config: config.get('env'),
url : config.get('url'),
user : req.user,
flash : req.flash()
};
next();
});
app.use(function (err, req, res, next) {
if (err.code !== 'EBADCSRFTOKEN'){
return next(err);
}
if(req.xhr){
return res.ok({payload: null}, '403 invalid csrf token');
}
// TODO handle CSRF token errors here
res.status(403);
res.send('form tampered with')
});
// This is never called when throwing errors like
// next(new Error('some error') or
// next({status: 500, message:'server error'});
app.use(function (err, req, res, next) {
console.error(err.stack);
// render an error page
});
};
The problem is, that your error handlers should always be at the end of your application stack. This means, that you can either move the error handler from your middleware to your app.js and use them after your requires (app.use()) or include your routes before your middleware.
Note: your error handler middleware MUST have 4 parameters: error, req, res, next. Otherwise your handler won't fire.
I had the same issue, spend the whole day on troubleshooting the issue. Finally, found a simple fix. This worked for me perfectly. You need to place the customer error handler right before the listener handler as below on the server instance file (App.js / server.js).
Good luck :)
app.use((error, req, res, next) => {
if (res.headersSent) {
return next(err)
}
res.status(500).send('INTERNAL SERVER ERROR !')
});
app.listen(3000, function() {
console.log('Node app is running on port 3000');
});
module.exports = app;
For handling errors that are thrown during asynchronous code execution in Express (versions < 5.x), you need to manually catch and invoke the in-built error handler (or your custom one) using the next() function.
app.get('/', (req, res, next) => {
setTimeout(() => {
try {
console.log('Async code execution.')
throw new Error('Unexpected error on async!')
} catch (error) {
// manually catching and propagating to your error handler
next(error)
}
}, 100)
})
// as a last declaration in your entrypoint
app.use((err, req, res, next) => {
// do some error handling here. if you do not call next, you must return a response from here.
})
Your error handler should always be at the end of your application stack.
Apparently it means not only after all app.use() but also after all your app.get() and app.post() calls.
If you don't want to write three parameters for every async router handler to be able to catch errors globally:
npm install express-async-errors
import 'express-async-errors';
app.get('/api/endpoint', async (req, res) => {
const user = await User.findByToken(req.get('authorization'));
if (!user) throw Error("access denied"); //will propagate to global error handler
});

Error #404 Page Not Working with Express

When I test my Error #404 page, I get the default "Not Found."
require('html');
var WebSocketServer = require('ws').Server
, http = require('http')
, fs = require('fs')
, express = require('express')
, app = express();
app.use(express.static(__dirname + '/public'));
var server = http.createServer(app);
server.listen(42069);
var MainServer = new WebSocketServer({server: server});
// Handle 404
app.use(function(req, res) {
res.status(404).render('/error/404.html',{title: "Error #404"});
});
However, it does work with
app.use(function(req, res) {
res.status(404).render('/error/404.html',{title: "Error #404"});
});
but I don't want to be redirected to my 404 page, I want it to be rendered on any non-existent address.
Any thoughts on how to get this to work?
Thanks!
You could try something like this after your route handling
app.get('/404', function(req, res, next){
// trigger a 404 since no other middleware
// will match /404 after this one, and we're not
// responding here
next();
});
app.use(function(req, res, next){
res.status(404);
// respond with html page
if (req.accepts('html')) {
res.render('404', { url: req.url });
return;
}
// respond with json
if (req.accepts('json')) {
res.send({ error: 'Not found' });
return;
}
// default to plain-text. send()
res.type('txt').send('Not found');
});
Place this after all your middleware
app.use(function(req,res){
res.status(404);
res.render('404page',{
});
});

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