Node.js route inheritance - javascript

I'm very new to node.js, specifically express.js. I'm working on express.js app to export some rest apis and I found that I have many dup codes that I would like to avoid this issue. I'm seeking a best practice solution. This is my made-up examples:
// app/routes/category.js
exports.create = function (req, res) {
categoryModel.create(someData, function (error, category) {
if (Util.isError(error)) {
res.send(400, error);
} else {
res.send(category);
}
});
};
// app/routes/product.js
exports.create = function (req, res) {
productModel.create(someData, function (error, product) {
if (Util.isError(error)) {
res.send(400, error);
} else {
res.send(product);
}
});
};
You can see that both my create routes codes are pretty much the same but just different model (product vs category) my questions are:
1) is there a way to do inheritance in node.js/express.js?
2) is there a way to do interface in node.js/express.js?
3) what is the best way to avoid duplicate code as above samples?
4) I was thinking of it is possible to have parent class and let both category and product classes inherits from parent. I really do not how it should be done in node.js
I read couple of node.js books but I don't see authors mentioned much about how to architect app in correct or at least best practice.
I read about Prototype.js, is it the right path to go?
Any thoughts and helps would be very appreciated!

There's no need for inheritance (especially since handlers have to be functions so it's hard for me to see how you can use inheritance here). You can simply create a function generator:
var generate_handler = function(model) {
return function(req, res) {
model.create(someData, function (error, obj) {
if (Util.isError(error)) {
res.send(400, error);
} else {
res.send(obj);
}
});
};
};
and then you do
// app/routes/category.js
exports.create = generate_handler(categoryModel);
// app/routes/product.js
exports.create = generate_handler(productModel);

Related

Run route level middleware conditionally

I've spent quite a bit of time looking to see if there is anything available that will run blocks of route level middleware conditionally.
Ideally the solution would require no changes to the middlewares, and they could just be passed in as an array.
I have just implemented a very crude solution but wanted to see what people think, what issues I'm likely to face and whether there is just a better solution to this.
An example of the problem I am trying to solve is middleware1 altered something on the request that meant middleware2, 3 & 4 do not need to run.
Here's my crude solution:
function conditional({ condition, field, value }, middleware) {
const conditions = {
eq: (_this, _that) => {
return _this === _that;
},
};
return (req, res, originalNext) => {
if (!conditions[condition](_.get(req, field), value)) {
return originalNext();
}
let i = 0;
function next() {
if (i === middleware.length) {
return originalNext();
}
if (typeof middleware[i] === 'function') {
i += 1;
middleware[i - 1](req, res, next);
}
}
next();
};
}
app.get(
'/test-conditional/:condition',
middleware1,
conditional({ condition: 'eq', field: 'params.condition', value: 'run' }, [
middleware2,
middleware3,
middleware4,
]),
middleware5,
async (req, res, next) => {
res.sendFile(path.join(__dirname + '/index.html'));
}
);
Any feedback or pointers in the right direction would be much appreciated.

Asynchronous callbacks in JavaScript

PHP dev here, I'm wrestling with NodeJS for a while and I still can't wrap my head around the idea of asynchrony is JS/Node.
Look at this (ExpressJS):
router.get('/:id', function (req, res, next) {
var id = req.params.id;
var db = new Firebase('some_firebase_db');
db.child("users/"+id).once('value', function (snapshot) {
var user = snapshot.val();
if (user) {
db.child("messages/"+id).once('value', function (snapshot) {
res.render('user/messages', {
'user': user,
'messages': snapshot.val()
});
});
} else {
res.render('404');
}
});
});
To make database value accessible for a res object I need to render views inside the fetching data callbacks.
So when I need, say, make 6 requests to my DB I will have to embed my res object in 6 callbacks?
Or is there different approach that will make the code more readable keeping it asynchronous?
Ultimately, I need a way to fetch data from db multiple times in one request that will not make my code look like Christmas tree.
You can make it more readable even without using async or Promise:
router.get('/:id', function(req, res, next) {
var id = req.params.id;
var db = new Firebase('some_firebase_db');
db.child("users/" + id).once('value', userResult);
function userResult(snapshot) {
var user = snapshot.val();
if (user) {
db.child("messages/" + id).once('value', messageResult);
} else {
res.render('404');
}
}
function messageResult(snapshot) {
res.render('user/messages', {
'user': user,
'messages': snapshot.val()
});
}
});
But using async or Promise would be a better solution.

`this` is undefined in expressJS route handler

groups.js
class groupsCtrl {
constructor() {
this.info = "test";
}
get(res, req) {
console.log("LOG ! ", JSON.stringify(this));
}
}
module.exports = new groupsCtrl(); //singleton
routes.js
var express = require('express');
var router = express.Router();
var groupsCtrl = require('controllers/api_admin/groups.js');
router.get('/groups/', groupsCtrl.get);
This logs LOG ! undefined
How can I have access to this in my controller class ?
You need to bind the method to the instance.
One solution:
router.get('/groups/', groupsCtrl.get.bind(groupsCtrl));
Another solution:
constructor() {
this.info = "test";
this.get = this.get.bind(this);
}
Or use something like es6bindall (which basically does the same as the code above, but is perhaps a bit more useful when you need to bind more than one method).
class groupsCtrl {
constructor() {
this.info = 'test';
}
get = (res, req) => {
console.log('LOG ! ', JSON.stringify(this));
};
}
You can just use arrow function to avoid boilerplate code
2020 Update
While both solutions posted by #robertkiep get the job dont, I want to emphasize that both look ugly and are not maintainable
Method 1 which does router.get('/groups/', groupsCtrl.get.bind(groupsCtrl)) looks really ugly when you have a large number of routes
Method 2 gets cumbersome when your controller has many routes
Since your example has only 1 route let me illustrate the problem
Using method 2
class AuthController {
constructor({ db, pgp, logger }) {
super({ db, pgp, logger })
this.postLogin = this.postLogin.bind(this)
this.postLogout = this.postLogout.bind(this)
this.postSignup = this.postSignup.bind(this)
this.postForgot = this.postForgot.bind(this)
this.getReset = this.getReset.bind(this)
this.postReset = this.postReset.bind(this)
}
postLogin(req, res, next) {
}
postLogout(req, res, next) {
}
async postSignup(req, res, next) {
}
async postForgot(req, res, next) {
}
async getReset(req, res, next) {
}
async postReset(req, res, next) {
}
}
Each time you add a new method, the constructor needs to be updated further
Method 3
This in my opinion is a lot cleaner, doesnt need maintenance and you can keep adding methods as you want
The idea is to use the Object.hasOwnPropertyName to get an array of all method names and then bind them programmatically
For example if you write Object.hasOwnPropertyName(AuthController.prototype) it will give you ALL NON STATIC methods in an array
In the example above you will get ['constructor', 'postLogin', 'postLogout'...]
If you call Object.hasOwnPropertyName(AuthController) you get STATIC methods
Lets invoke them programmatically
This controller requires little to no maintenance except keep the static and non static methods in mind, remove the constructor by filtering it out and then invoke bind on each
class AuthController {
constructor({ db, pgp, logger }) {
super({ db, pgp, logger })
this.postLogin = this.postLogin.bind(this)
this.postLogout = this.postLogout.bind(this)
this.postSignup = this.postSignup.bind(this)
this.postForgot = this.postForgot.bind(this)
this.getReset = this.getReset.bind(this)
this.postReset = this.postReset.bind(this)
Object.getOwnPropertyNames(AuthController.prototype)
.filter((propertyName) => propertyName !== 'constructor')
.forEach((method) => (this[method] = this[method].bind(this)))
}
postLogin(req, res, next) {
}
postLogout(req, res, next) {
}
async postSignup(req, res, next) {
}
async postForgot(req, res, next) {
}
async getReset(req, res, next) {
}
async postReset(req, res, next) {
}
}
The answer above is great, I want to add a little bit to help clarify:
Assume we have a class:
class TClass {
constructor(arg) {
this.arg = arg
}
test() {
console.log(this.arg)
}
}
This will NOT work:
const t = new TClass("test")
const method = t.test // method is simply a reference without context
method() // 'this' is not defined
This will work:
const t = new TClass("test")
t.test() // log 'test'
And the reason is like the comments above, the reference to the function doesn't have a context

Express js manipulate data before render view

I am new using nodejs and express
and was wondering what is the best practice to manipulate data before render a view. Currently i want to set some variables based on the retrieved data to render the view. So far this is what i have, but I am not sure if this is the best practice or if there is any better way to do it.
var request = require('request');
module.exports = function(req, res, next) {
request.get('http://nout.nout-app.com/getAccessVIPForUserId/' + req.params.id, function(err, resp, body) {
var bodyResp = JSON.parse(body);
bodyResp.data.forEach(function(el, index, array){
if(el.access_status === '1') {
el.status = 'success';
} else {
el.status = 'warning';
}
if(el.access_friend === '1') {
el.access_friend = 'yes';
} else {
el.access_friend = 'no';
}
});
console.log(bodyResp.data);
if(err || (typeof bodyResp.data === 'undefined' || bodyResp.data === null)) {
res.render('error', {
message: bodyResp.reason ? bodyResp.reason : 'Something went wrong',
error: {
status: 500
}
});
} else {
res.render('profile', {
intern: true,
user: req.user,
invitations: bodyResp.data
});
}
});
};
I appreciate if you guys could give me guide in this and also suggest some good material to improve. Regards.
Yes, .forEach is blocking (synchronous), but it is extremely fast. In general you don't need to worry about for basic data manipulation like that. Remember - async doesn't make something take less time, it just gives other things the ability to keep happening in the mean time.
If you really want to make your loop async, have a look at the async module. async.each is an async version of .forEach

Handling timeouts with Node.js and mongodb

I am currently testing how some code stands up against the following scenario:
Node.js application is started and successfully establishes a connection to mongodb
After it has successfully setup a connection, the mongodb server dies and all subsequent requests fail
To do this I have got the following code which makes use of the official driver (found here: https://github.com/mongodb/node-mongodb-native) :
MongoClient.connect('mongodb://localhost:27017/testdb', function(err, db) {
app.get('/test', function(req, res) {
db.collection('users', function (err, collection) {
console.log(err);
if (err) {
// ## POINT 1 ##
// Handle the error
}
else {
collection.find({ 'username': username }, { timeout: true }).toArray(function(err, items) {
console.log(err);
if (err) {
// ## POINT 2 ##
// Handle the error
}
else {
if (items.length > 0) {
// Do some stuff with the document that was found
}
else {
// Handle not finding the document
}
}
});
}
});
});
});
As the mongodb server is no longer running when the request is being handled, I'd made the assumption that at either the points which I have labelled ## POINT 1 ## or ## POINT 2 ##, it would return an error indicating a timeout; this however, isn't the case.
I have tried a number of different settings (including one you can see here that explicitly allows the cursor to timeout), however I cannot seem to enable it in any way. In every configuration I've tried Node.js will simply keep waiting for the find() operation to callback and it never does.
If I start the Node.js application before running mongodb, it catches the error in the connect callback fine, but if the connection dies after that it doesn't seem to handle it in any way.
Is there a setting I am missing or is there no way to detect connections being terminated after they've been established?
Edit: just to be clear, the username variable used in the find method is actually declared in my full code, the code I've put in this post is a cut down version to illustrate the structure and error checking.
UPD:
Based on this post, looks like they've deployed fix that will do the same as what we do here. Not sure if this is already within npm (15.10.13). https://github.com/mongodb/node-mongodb-native/issues/1092#ref-commit-2667d13
After some investigation I've managed to understand what is going on there:
Every time you call any method to deal with database (find, update, insert, etc.) it creates cursor, that has own ID and registers itself to EventEmitter of Db for being called back later. In meantime it registers itself to _notReplied object within same CallBackStore.
But once connection is closed, I couldn't locate anything that would iterate through _notReplied cursors and would trigger them with errors or any logic with timers (it still might be somewhere there). So I've managed to write small work around, that does force triggers cursors with error when DB emits close event:
new mongodb.Db('testdb', new mongodb.Server('localhost', 27017, { }), { safe: true }).open(function (err, db) {
if (!err) {
db.on('close', function() {
if (this._callBackStore) {
for(var key in this._callBackStore._notReplied) {
this._callHandler(key, null, 'Connection Closed!');
}
}
});
// ...
} else {
console.log(err)
}
});
I recommend using first approach instead of MongoClient. Reasons are few: for example when you close connection and then call .find it will properly trigger error in callback, while with MongoClient it won't.
If you are using MongoClient:
MongoClient.connect('mongodb://localhost:27017/testdb', function(err, db) {
if (!err) {
db.on('close', function() {
if (this._callBackStore) {
for(var key in this._callBackStore._notReplied) {
this._callHandler(key, null, 'Connection Closed!');
}
}
});
// ...
} else {
console.log(err);
}
});
What this will do? Once connection is closed, it will iterate through All _notReplied cursors and trigger events for them with error Connection Closed!.
Test case:
items.find({ }).toArray(function(err, data) {
if (!err) {
console.log('Items found successfully');
} else {
console.log(err);
}
});
db.close();
That will force close database connection and trigger close event that you handle earlier and will make sure that cursor will be closed.
UPD:
I've added Issue on GitHub: https://github.com/mongodb/node-mongodb-native/issues/1092 we'll see what they say regarding this.
I had the same problem, and found this page from google.
But your choosed answer didn't resolve the problem and it is as same as you, this._callBackStore can't use
but i tried to wrap the Mongo, and it seems work fine
var MongoClient = require('mongodb').MongoClient;
var mongo = {};
mongo.init = function() {
MongoClient.connect('mongodb://localhost:27017/testdb', function(err, db) {
if (err) {
mongo.DB = '';
} else {
mongo.DB = db;
}
db.on('close', function() {
mongo.DB = '';
});
db.on('reconnect', function() {
mongo.DB = db;
});
}
}
mongo.getdb = function(callback) {
if (mongo.DB) {
callback(null, mongo.DB);
} else {
callback('can not connect to db', null);
}
}
module.exports = mongo;
firstly start server and init() it
and then you can require it and use
mongo.getdb(function(err, db) {
if (err) {
console.log(err);
} else {
db.collection('user').find({'xxx':'xxx'}).toArray(function(err, items) {
console.log(items);
});
}
});
After some further investigation, it seems that you can't specify "offline" timeouts such as in the scenario outlined above. The only timeout that can be specified is one which informs the server to timeout the cursor after 10 minutes of inactivity, however as in the scenario above the connection to the server is down this does not work.
For reference, I found the information here: https://github.com/mongodb/node-mongodb-native/issues/987#issuecomment-18915263 by who I believed to be one of the main contributors to the project.
I'm making api with Hapi and Mongodb (w/o mongoose). Features:
Start responding to API request only if mongo db is available
Stop responding if mongo dies during cycle
Re-start when mongo available again
Keep single connection for all requests
Combining some ideas from other answers and this post https://productbuilder.wordpress.com/2013/09/06/using-a-single-global-db-connection-in-node-js/ my approach is this:
server.js
Utilities.initializeDb(() => {
server.start((err) => {
if (err) throw err;
console.log('Server running at:', server.info.uri);
});
}, () => {
server.stop((err) => {
if (err) throw err;
console.log('Server stopped');
});
});
Utilities.js
"use strict";
const MongoClient = require('mongodb').MongoClient;
const MongoUrl = 'mongodb://localhost:27017/db';
export const Utilities = {
initializeDb: (next, onCrash) => {
const ConnectToDatabase = (params) => {
MongoClient.connect(MongoUrl, (err, db) => {
if (err !== null) {
console.log('#t4y4542te Can not connect to mongo db service. Retry in 2 seconds. Try #' + params.retry);
console.error(err);
setTimeout(() => {
ConnectToDatabase({retry: params.retry + 1});
}, 2000);
} else {
db.on('close', () => {
onCrash();
console.log('#21df24sf db crashed!');
ConnectToDatabase({retry: 0});
});
global.db = global.db || db;
next();
}
});
};
ConnectToDatabase({retry: 0});
}
};
I'm exporting db connection to global space. It feels like not best solution, but I had projects where db connection was passed as param to all modules and that sucked more. Maybe there should be some modular approach where you import db connection where you need it, but in my situation i need it almost everywhere, I would have to write that include statement in most files. This API is pointless w/o connection to db, so I think it might be best solution even if I'm against having something flying magically in global space..

Categories