How to write a middleware class in Node.js - javascript

I have researched the topic for hours on Google and books and I could only find very specific implementations. I'm struggling to write a simple Middleware class in node JS with only plain vanilla javascript (no additional module like async, co,..). My goal is to understand how it works not to get the most optimised code.
I would like something as simple as having a string and adding new string to it thru the use of middleware.
The class
"use strict";
class Middleware {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
}
executeMiddleware(middlewares, msg, next) {
// This is where I'm struggling
}
run(message) {
this.executeMiddleware(this.middlewares, message, function(msg, next) {
console.log('the initial message : '+ message);
});
}
}
module.exports = Middleware;
A possible usage
const Middleware = require('./Middleware');
const middleware = new Middleware();
middleware.use(function(msg, next) {
msg += ' World';
next();
});
middleware.use(function(msg, next) {
msg += ' !!!';
console.log('final message : ' + msg);
next();
});
middleware.run('Hello');
As a result the msg variable will end up being : 'Hello World !!!'

For those looking for a working example.
// MIDDLEWARE CLASS
"use strict";
let info = { msg: '' };
class Middleware {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
}
executeMiddleware(middlewares, data, next) {
const composition = middlewares.reduceRight((next, fn) => v => {
// collect next data
info = data;
fn(info, next)
}, next);
composition(data);
}
run(data) {
this.executeMiddleware(this.middlewares, data, (info, next) => {
console.log(data);
});
}
}
module.exports = Middleware;
Usage example :
// index.js
const Middleware = require('./Middleware');
const middleware = new Middleware();
middleware.use(function(info, next) {
info.msg += ' World';
next();
});
middleware.use(function(info, next) {
info.msg += ' !!!';
next();
});
// Run the middleware with initial value
middleware.run({msg: 'Hello'});

The accepted answer has a few issues, namely unused/uneeded variables. Also id just like to post an alternative answer since thats what stackexchange is for.
Usage example is the same
class Middleware {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
}
executeMiddleware(data, done) {
this.middlewares.reduceRight((done, next) => () => next(data, done), done)
(data);
}
run(data) {
this.executeMiddleware(data, done => console.log(data));
}
}

class InputValidation {
constructor() {
}
getData(req, res, next) {
console.log(req)
//your stuff here
next();
}
}
module.exports = new InputValidation();
//Just import into main module and make a middleware
const InputValidation = require("./validation/inputValidation");
app.use(InputValidation.getData);

Related

create middlware with input argument in nodejs

i want to create a misslware in nodejs for access Level , i create this middlware :
class AccessUser extends middlware {
async AccessUser(access,req, res, next) {
const getTokenFrom = (req) => {
const authorization = req.headers["authorization"];
if (authorization && authorization.toLowerCase().startsWith("bearer ")) {
return authorization.substring(7);
}
return null;
};
const token = getTokenFrom(req);
if (token) {
jwt.verify(token, "shhhhh", (err, decoded) => {
if (err) return new ForbiddenResponse().Send(res);
let permission = decoded.info.permission;
let item = permission.find((x) => x.permissionId == access);
if (!item) {
return new ForbiddenResponse().Send(res);
} else {
next();
}
});
}
}
}
i add the argument name access to input of AccessUser in this middlware :
async AccessUser(access,req, res, next)
and i want to need compare the access with x.permissionId . but it show me this error :
(node:2168) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'authorization' of undefined
this error for this line :
const authorization = req.headers["authorization"];
and i use this middlware by this :
router.post(
"/Create",
access.AccessUser("Role:Add")
);
now whats the problem ? how can i use the middllware with input argument ?????
AccessUser is not a express middleware, the method signature has to be (req,res,next).
You can get around this if you define AccessUser as a factory function (there's no need to define it async as you're not awaiting any async call):
class AccessUser {
accessUser(access) {
return function (req, res, next) {
const getTokenFrom = (req) => {
const authorization = req.headers["authorization"];
if (authorization && authorization.toLowerCase().startsWith("bearer ")) {
return authorization.substring(7);
}
return null;
};
const token = getTokenFrom(req);
if (token) {
jwt.verify(token, "shhhhh", (err, decoded) => {
if (err) return new ForbiddenResponse().Send(res);
let permission = decoded.info.permission;
let item = permission.find((x) => x.permissionId == access);
if (!item) {
return new ForbiddenResponse().Send(res);
} else {
next();
}
});
}// TODO: handle case if no token exists?
}
}
}
module.exports = AccessUser;
Then use it like this:
const AccessUserMiddleware = require('./path-to-access-middleware');
const AccessUser = new AccessUserMiddleware();
app.get('/', AccessUser.accessUser("Role:Add"));

How to convert string "req.body" to data (function)?

I make a function checkCompanyPermit with paramater companyIdSource and array allowed.
Example:
companyIdSouce: "req.body.companyId", "req.params.companyId"...
allowed: "user", "admin"...
With parameter companyIdSource as string, I want to convert it to data. It's worked if I use eval(companyIdSource) but it's bad. How can I do another?
I try use Function("return " + companyIdSource)() but it return an error: req is not defined.
const checkCompanyPermit = (companyIdSource, ...allowed) => {
return async (req, res, next) => {
try {
const companyId = eval(companyIdSource) //Bad code, change another code
const company = await Company.findById(companyId)
//... some code
} catch (error) {
next(error)
}
}
}
checkCompanyPermit("req.body.companyId", "manager")
Since you already have access to the req object in your middleware, there is no need to pass the full string representation for req.body.companyId, just the property you need to check will suffice. Use the bracket notation to access the value from req.body object i.e.
const checkCompanyPermit = (companyIdSource, allowed) => {
return async (req, res, next) => {
try {
const companyId = req.body[companyIdSource]
const company = await Company.findById(companyId)
//... some code
} catch (error) {
next(error)
}
}
}
checkCompanyPermit("companyId", "manager")
It's Working For You.
const ObjectId = require('mongodb').ObjectId;
const checkCompanyPermit = (companyIdSource, ...allowed) => {
return async (req, res, next) => {
try {
const companyId = ObjectId('companyIdSource') //Replace here new code
const company = await Company.findById(companyId)
//... some code
} catch (error) {
next(error)
}
}
}
checkCompanyPermit("req.body.companyId", "manager")

Check multi permission by middleware

I already resolve this problem. I find Express.js role-based permissions middleware and use it. It's great and I will re-write my code!
I'm want to check multi permistion but it not working for me.
I create 3 middlwares to check Permistion: requiredAuth, checkAdmin and checkCompanyManager.
Model: User: {name, permistion, isBlocked, company: {id, userPermistion}}
requiredAuth funtion will check and find signedUser, and set it tores.locals.user
const checkAdmin = (req, res, next) => {
let user = res.locals.user
if (user.permission === 2) next()
else res.json({errors: "Only admin can do this action"})
}
const checkCompanyManager = (req, res, next) => {
let user = res.locals.user
let companyId = req.body.companyId ? req.body.companyId : req.query.companyId
if (user.company.id && user.company.id.equals(companyId)
&& user.company.userPermistion === 1) next()
else res.json({errors: "Only company member can do this action"})
}
And last, I use all in router to check action block user (Only admin or company manager can block user)
router.post('/admin/block-by-ids',
requiredAuth,
checkAdmin || checkCompanyManager,
userController.blockByIds
)
But it's not working, because if checkAdmin wrong, it's break and return json, not run checkCompanyManager I can solve this problem as follows:
router.post('/admin/block-by-ids',
requiredAuth,
(req, res, next) => {
let user = res.locals.user
let companyId = req.body.companyId
if ((user.permission === 2) ||
(user.company.id && user.company.id.equals(companyId) &&
user.company.userPermistion === 1)) {
next()
} else next("Only admin or company manager can do this action")
},
userController.blockByIds
)
But it's not fun! I want only use middleware to check and do not want to write code again. How can I do this? I want an idea from you!
The || operator does not do what you think it does. It returns the first truthy value:
var a = 1 || 2; // a is 1
What you need is an OR middleware. Something like:
function or (middleware1, middleware2) {
return function (req, res, next) {
var alreadyCalledNext = false;
function resolve () {
if (!alreadyCalledNext) {
alreadyCalledNext = true;
next();
}
}
middleware1(req,res,resolve);
middleware2(req,res,resolve);
}
}
router.post('/admin/block-by-ids',
requiredAuth,
or(checkAdmin, checkCompanyManager),
userController.blockByIds
)
But the above implementation runs into another problem. Once you've sent res.json you cannot send another response. So if either checkAdmin or checkCompanyManager fails you need to stop them from sending res.json unless both fails. So you need to stub res and pass a fake res (just like what we did with next above):
function or (middleware1, middleware2) {
return function (req, res, next) {
var alreadyCalledNext = false;
function resolve () {
if (!alreadyCalledNext) {
alreadyCalledNext = true;
next();
}
}
var jsonCount = 0;
var fakeRes = {
locals: res.locals,
json: function (data) {
jsonCount ++;
if (jsonCount >= 2) { // both must fail for OR to fail
res.json(data);
}
}
}
middleware1(req,fakeRes,resolve);
middleware2(req,fakeRes,resolve);
}
}
This should work.
IMHO the solution above feels over-engineered. I would personally make checkAdmin and checkCompanyManager regular functions returning boolean then wrap them in a checkPermissions middleware:
const isAdmin = (req,res) => {
let user = res.locals.user
return user.permission === 2
}
const isCompanyManager = (req,res) => {
let user = res.locals.user
let companyId = req.body.companyId ? req.body.companyId : req.query.companyId
return user.company.id && user.company.id.equals(companyId) && user.company.userPermistion === 1
}
const checkPermissions = function (checks) {
return (req, res, next) => {
// Call next if any check passes:
for (let i=0; i<checks.length; i++) {
if (checks[i](req,res)) return next();
}
res.json({errors: "You don't have authorization to do this action"})
}
}
router.post('/admin/block-by-ids',
requiredAuth,
checkPermissions([isAdmin, isCompanyManager]),
userController.blockByIds
)

How do I reuse a RabbitMQ connection and channel outside of the "setup structure" with the amqp library?

I am trying to build a simple node.js client using the amqp library, that opens a single connection and then a single channel to a RabbitMQ server. I want to reuse the same connection and channel to send multiple messages. The main problem is, that I don't want to write my entire code inside the callback function of the ceateChannel() function.
How do I reuse the channel outside of the callback function and make sure the callback function has finished before I use the channel?
I've tried both the callback way and the promise way but I can't make either of them work. When using the callback method I run into the described problem.
When using promises, I have the problem that I can't keep a reference of the connection and channel outside of the .then() function because the passed variables get destroyed after setting up the connection and channel.
amqp.connect('amqp://localhost', (err, conn) => {
if (err !== null) return console.warn(err);
console.log('Created connection!');
conn.createChannel((err, ch) => {
if (err !== null) return console.warn(err);
console.log('Created channel!');
//this is where I would need to write the code that uses the variable "ch"
//but I want to move the code outside of this structure, while making sure
//this callback completes before I try using "ch"
});
});
amqp.connect('amqp://localhost').then((conn) => {
return conn.createChannel();
}).then((ch) => {
this.channel = ch;
return ch.assertQueue('', {}).then((ok) => {
return this.queueName = ok.queue;
});
}).catch(console.warn);
why you don't use async\await ?
const conn = await amqp.connect('amqp://localhost');
const ch = await conn.createChannel();
// after that you can use ch anywhere, don't forget to handle exceptions
Also if you use amqplib, don't forget to handle close and internal error events, for example like this:
conn.on('error', function (err) {
console.log('AMQP:Error:', err);
});
conn.on('close', () => {
console.log("AMQP:Closed");
});
Try with a class, like this:
RabbitConnection.js
const amqp = require('amqplib');
const RabbitSettings = {
protocol: 'amqp',
hostname: 'localhost',
port: 5672,
username: 'guest',
password: 'guest',
authMechanism: 'AMQPLAIN',
vhost: '/',
queue: 'test'
}
class RabbitConnection {
constructor() {
RabbitConnection.createConnection();
this.connection = null;
this.channel = null;
}
static getInstance() {
if (!RabbitConnection.instance) {
RabbitConnection.instance = new RabbitConnection();
}
return RabbitConnection.instance;
}
//create connection to rabbitmq
static async createConnection() {
try {
this.connection = await amqp.connect(`${RabbitSettings.protocol}://${RabbitSettings.username}:${RabbitSettings.password}#${RabbitSettings.hostname}:${RabbitSettings.port}${RabbitSettings.vhost}`);
this.channel = await this.connection.createChannel();
this.channel.assertQueue(RabbitSettings.queue);
console.log('Connection to RabbitMQ established');
} catch (error) {
console.log(error);
}
}
//send message to rabbitmq queue
static async sendMessage(message, queueName) {
try {
let msg = await this.channel.sendToQueue(queueName, Buffer.from(message));
console.log('Message sent to RabbitMQ');
return msg;
} catch (error) {
console.log(error);
}
}
}
module.exports = { RabbitConnection };
ServerExpress.js
const express = require('express');
const { RabbitConnection } = require('./RabbitConnection');
const serverUp = () => {
const app = express();
app.get('/', (req, res) => {
RabbitConnection.sendMessage('Hello World', 'test');
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
};
module.exports = { serverUp };
index.js
const { RabbitConnection } = require("./RabbitConnection");
const { serverUp } = require("./ServerExpress");
serverUp();
RabbitConnection.getInstance();

Import in node js error

How can I import this code in module.exports ?
I'm pretty new in node js and in js.
I want that this code could be used in other routes
cache = (duration) => {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
res.send(cachedBody)
return
} else {
res.sendResponse = res.send
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body)
}
next()
}
}
}
How can I export it?
I was something like :
module.exports = cache = (duration) => {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
res.send(cachedBody)
return
} else {
res.sendResponse = res.send
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body)
}
next()
}
}
}
Also I try:
module.export = {
cache: function(duration) {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
res.send(cachedBody)
return
} else {
res.sendResponse = res.send
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body)
}
next()
}
}
}
}
But when I try to use it in a get request:
var expCache = require('../../middleware/cache');
router.get('/:sid/fe',expCache.cache(3000),function(req,res) {
It brings:
TypeError: expCache.cache is not a function
Regards
You need to export an object, if you're expecting to be able to call expCache.cache:
module.exports = {
cache: // your function
}
However, if you want to keep your exported module as it is, just call it like this instead:
// inside middleware/cache:
// module.exports = cache = (duration) => {
var expCache = require('../../middleware/cache');
// you can call it as just a function
router.get('/:sid/fe', expCache(3000), function(req,res) {
Try
var expCache = require('../../middleware/cache');
router.get('/:sid/fe',expCache(3000),function(req,res) {..
You're exporting your cache function already, not an object containing it (which is how you try to use it for your router).

Categories

Resources