Express.js - wrap every middleware/route in "decorator" - javascript

I have Express.js instance and couple of routes which I want to wrap in some function. Example:
const wrapper = (route) => {
return (req, res, next) => {
let result = route(req, res, next);
// do some independent processing
}
};
app.get('/', wrapper((req, res, next) => {
// respond to request somehow
}));
While this works fine, I don't like the idea to explicitly call wrapper on every route or middleware which requires such processing.
Is there any way to be able to wrap every required route/middleware in certain wrapper (given that wrapper function can check that this route/middleware needs to be wrapped) implicitly (via Express.js extension, monkey-patching or some special middleware)?
UPDATE:
More solid example. Let's assume I want to make an async router functions. But I don't want to catch errors in each and every route function. So I wrap them up:
const wrapper = func => (req, res, next) => {
const promise = func(req, res, next);
if (promise.catch) {
promise.catch(err => next(err));
}
next();
};
app.get('/one', wrapper(async (req, res, next) => {
// respond to request somehow
}));
app.get('/two', wrapper(async (req, res, next) => {
// respond to request somehow
}));
app.get('/three', wrapper(async (req, res, next) => {
// respond to request somehow
}));
// and so on...
app.use((err, req, res, next) => {
// do something with intercepted error
});
This explicit wrapper around all routes is actually the thing I want to get rid of.

It turned out to be a bit of a PITA because, ultimately, Express doesn't propagate the return value of a route handler function.
This is what I came up with (a monkey-patch):
const Layer = require('express/lib/router/layer');
const handle_request = Layer.prototype.handle_request;
Layer.prototype.handle_request = function(req, res, next) {
if (! this.isWrapped && this.method) {
let handle = this.handle;
this.handle = function(req, res, next) { // this is basically your wrapper
let result = handle.apply(this, arguments);
// do some independent processing
return result;
};
this.isWrapped = true;
}
return handle_request.apply(this, arguments);
};
I would probably suggest using a similar approach as express-promise-router though, which implements a drop-in replacement for Express' Router. However, it's not implicit.

Why not just use next()?
You can add stuff on req like
app.get('/', (req, res, next) => {
req.somestupidfieldthatidontevenknowwhyinamedthisway = 42;
next();
});
app.get('/', (req, res, next) => {
//req.somestupidfieldthatidontevenknowwhyinamedthisway is now accessible as 42
var valueFromPreviousMiddleware = req.somestupidfieldthatidontevenknowwhyinamedthisway;
.....
});

You could wrap middleware and router as below
function wrapper(func) {
return function inner(req, res, next) {
const start = Date.now();
func(req, res, function () {
let elapsedMS = Date.now() - start
console.log('time elapsed for function ' + func.prototype.constructor.name + ' is ' + elapsedMS)
next.apply(this, arguments);
});
};
}
var originalAppUse = app.use;
app.use = function () {
lastArg = arguments.length - 1;
if (typeof arguments[lastArg] === 'function') {
arguments[lastArg] = wrapper(arguments[lastArg])
}
originalAppUse.apply(this, arguments)
}

Related

Express Middlewares execute their arguments, while JavaScript functions doesn't

I want to write an express middleware, I'm calling it express-stack-player.
What this middleware does is that it gives your middlewares access to the objects (req, res, next), by getting the benefit of the JavaScript scopes ex:
The following is a very simplified version of its code, which sums up its core job of it:
const stackPlayer = (stacks) => (req, res, next) => {
// #param stacks: function
const stacksValue = stacks(req, res, next);
for (middleware of stacksValue) {
middleware(req, res, next);
}
};
It used to work during my project which I'm working on until eventually, I discovered an unwanted behaviour by running the following test:
const express = require("express");
const app = express();
const setter = (req, res, next) => {
console.log("hello world");
req.a = {};
req.a.b = 1;
next();
};
const readSomething = (something) => (req, res, next) => {
console.log(something);
};
const stackPlayer = (stacks) => (req, res, next) => {
// #param stacks: function
const stacksValue = stacks(req, res, next);
for (middleware of stacksValue) {
middleware(req, res, next);
}
};
app.get(
"/",
stackPlayer((req, res, next) => [setter, readSomething(req.a.b)])
);
console.clear();
app.listen(4000, () => console.log("listening on port 4000"));
When I run npm start, it prints the following in the terminal just normally:
But when I request GET /, the following error prints out:
I was testing this JavaScript code in the browser to see its behavior:
const obj = {}
const setOnObj = () => {
obj.a = {}, obj.a.b = 1
}
const readSomething = (arg) => {
console.log(arg)
}
setOnObj()
readSomething(obj.a.b)
If you click on Run code snippet, the output will be 1. So why does my express-stack-player package have a different behavior?
By the way, the package is on npm, and it can be downloaded by running
npm i #samislam/switcher
In the past, I made a bigger package called switcher, but now I want to move the core logic of executing the middlewares into a separate package, and I'll call it express-stack-player (if the name would be available by then).

Choosing which middleware to run based on request in Express JS

I would like to know how to choose between two different middleware functions, depending on the request for the endpoint. It could look something like this:
router.post("/findAvailableAgents", middleware1 || middleware2, (req, res) => {
// endpoint body
})
You could use another middleware which decides whether to choose middleware1 or middleware2
const decideMiddleware = (req, res, next) => {
if(condition) {
return middleware1(req, res,next)
} else {
return middleware2(req, res,next)
}
}
And use it in your code
router.post("/findAvailableAgents", decideMiddleware, (req, res))
There is two ways of achieve optional middleware behaviour:
1) Create another middleware, that checks condition and then passes all the parameters into the desired middleware. Example:
const middlewareStrategy = (req,res,next) => {
if(req.body.token1){
return middleware1(req,res,next);
}
return middleware2(req,res,next);
};
router.post("/findAvailableAgents", middlewareStrategy, handler);
2) Make middleware logic execution in a condition-driven manner. Example:
const middleware1 = (req,res,next) => {
if(req.body.token){
// execute some logic, then
return next();
}
// skip this middleware
next();
};
router.post("/findAvailableAgents", middleware1, middleware2, handler);
Now you can add multiple middleware using a below peice of code
app.get('/',[middleware.requireAuthentication,middleware.logger], function(req, res){
res.send('Hello!');
});

Can a POST request accept two callbacks and can the first pass data to the second?

I have the following .post() request:
const express = require('express');
const router = express.Router();
const search_controller = require('../controllers/searchController');
const result_controller = require('../controllers/resultController');
//Search Routes
router.post('/', search_controller.search_create_post);
module.exports = router;
Could I add a second callback to it so that the first callback is run, then the second callback as such:
router.post('/', search_controller.search_create_post, result_controller.result_create_post)
Would I need a next() somewhere in those create functions? And could I also pass data from the search_create_post callback to the result_create_post callback? I would want to pass in the newly created Search object's id.
My current search_controller.search_create_post function is a such:
exports.search_create_post = (req, res, next) => {
let newSearch = new Search({ search_text: req.body.search_text });
newSearch.save((err, savedSearch) => {
if (err) {
console.log(err);
} else {
res.send(savedSearch);
}
})
};
You might be able to use like this (based on how your functions are written):
// option A
router.post('/', search_controller.search_create_post, result_controller.result_create_post)
// options B
router.post('/', search_controller.search_create_post)
router.post('/', result_controller.result_create_post)
If search needs to pass data to result, you could set req.search_data in search_create_post and then get the value in result_create_post.
Take a look at https://expressjs.com/en/guide/using-middleware.html. There are a few good examples on this page.
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id)
next()
}, function (req, res, next) {
res.send('User Info')
})
// handler for the /user/:id path, which prints the user ID
app.get('/user/:id', function (req, res, next) {
res.end(req.params.id)
})
based on your comment below:
You might be able to do this...
exports.search_create_post = (req, res, next) => {
let newSearch = new Search({ search_text: req.body.search_text });
newSearch.save((err, savedSearch) => {
if (err) {
console.log(err);
} else {
req.searchData = savedSearch;
}
next();
})
};

how get the req, res objects in the callbacks

I like to get the req, res, next objects outside the middleware function.
sample middleware - sample.js:
var app = express();
....
.....
....
var updateUserInput = {
init:function(){
get_data_from_db(params, function(){
// want req, res here.
})
}
}
const processUserInput = function(req, res, next){
updateUserInput.init();
}
module.exports = processUserInput;
You should be able to pass those into the init function right away:
var updateUserInput = {
init:function(req,res,next){
get_data_from_db(params, function(){
// You will have req and res available here
// finish processing and call next()
})
}
}
const processUserInput = function(req, res, next){
updateUserInput.init(req,res,next);
}

Providing Custom Params In Express Middleware

I am having a problem with my Node.js app. In short I want to pass custom parameters into my middleware function other than just req, res, and next.
Middleware file:
var DB = require('./DB.js');
function requirePermissions(e) {
console.log('nope')
}
module.exports = requirePermissions;
Route:
router.post('/posts', requirePermissions('post_creation'), function(req, res) {
var o = req.body,
title = o.post.title,
content = o.post.content;
res.send('made it');
});
I have confirmed that using function requirePermissions(req, res, next) {} will work, but I do not understand how to include my own parameters.
Your function requirePermissions should return another function which will be the actual middleware:
function requirePermissions(e) {
if (e === 'post_creation') {
return function(req, res, next) {
// the actual middleware
}
} else if (e === 'something_else') {
return function(req, res, next) {
// do something else
}
}
}
You can also do it like that:
function requirePermissions(e) {
return function(req, res, next) {
if ('session' in req) {
if (e === 'post_creation') {
// do something
} else if (e === 'something_else') {
// do something else
}
}
}
}
You can just create an anonymous function for your middleware that lets you call your actual function with some additional arguments:
router.post('/posts', function(req, res, next) {
requirePermissions('post_creation', req, res, next);
}, function(req, res) {
var o = req.body,
title = o.post.title,
content = o.post.content;
res.send('made it');
});
Or, you can use .bind() to preprend arguments:
router.post('/posts', requirePermissions.bind('post_creation'), function(req, res) {
var o = req.body,
title = o.post.title,
content = o.post.content;
res.send('made it');
});
This will call your requirePermissions() functions with four arguments like this:
requirePermissions('post_creation', req, res, next)

Categories

Resources