Basic MVC principles using Express.JS - javascript

express.js provides you with a decent barebone system to implement a standard MVC development pattern. However most tutorials i've seen apply controller logic in a routes file or a global app file.
In an ideal world:
Model - Manages fundamental behaviours and data
Controller - Sends commands to the model and the view
View - Renders data from the model
currently i have the following:
routes/index.js - route pointing to an action
router.get('/hotels', function(req, res, next) {
hotels.run(req, res, next);
next();
});
controllers/hotels.js - controller sending a command to a model
module.exports = {
run: function(req, res, next) {
var users = new require('../models/hotels');
users.run(function(callback) {
res.render('hotels', { title: 'Hotels page', users: callback });
});
}
}
models/hotel.js - model requesting data
module.exports = {
run: function(callback) {
connection.query(sql, function(err, rows, fields) {
callback(rows);
//console.log(rows);
});
}
}
No matter what i try, i can't get the data from the model to return to the controller to then be passed to the view. I understand there are probably multiple errors within the above code as i'm new to express. But the fundamentals should be ok, and i'm hoping it's something obvious as to why i can't return the model data,as all logic above other than the callback works.

I believe i have solved this issue, if people are looking to use a similar MVC approach on an express.js project.
routes.js - changed to match Kevin's cleaner method.
router.get('/hotels', hotels.run);
controller/hotels.js -
module.exports = {
run: function(req, res, next) {
var users = new require('../models/hotels');
users.run(function(err, callback) {
res.render('hotels', { title: 'Hotels page', users: callback });
});
}
}
models/hotel.js
module.exports = {
run: function(callback) {
connection.query(sql, function(err, rows, fields) {
callback(err, rows);
});
}
}
so now the model returns the query as requested by the controller, giving the controller the ability to pass the data to the view.

Related

It is possible to enhance the express.js req and res variables without using a middleware function?

I'm working in a restful service using express.js and i want to enhance the req and res variables so for example you could write something like
app.use(function (req, res, next) {
res.Ok = function (data) {
res.status(200).send(data);
};
res.InternalError = function (err) {
res.status(500).send(err);
};
});
And later
router.get('/foo', function (req, res) {
res.Ok('foo');
})
This will send 'foo' in the body of the response and set the status code to 200 and is working perfectly.
My first question is if it is possible to add such functionality without a middleware function, lets say in a property or the prototype of the app variable?
The second question is if there are performance issues if you add many functionality with middleware functions at the app level. Are this functions attached to the request and response object per request or once on the application startup?
I know the Sails framework already do this but I'm wondering if they use middleware functions as well.
I keep digging and turns out that the request and response object are exposed in express using the __proto__ property.
var express = require('express'),
app = express();
app.response.__proto__.foo = function (data) {
this.status(200).send(data);
};
And later in the router
router.get('/foo', function (req, res, next) {
res.foo('test');
});
This will print test in your browser so it is possible to add functionality without using any middleware.
Note: I'm sure there are some drawbacks to this approach (overwriting express predefined properties, for example) but for testing purposes and adding very simple functionality I think is slightly better in terms of performance.
I'm not aware of any other way than using middleware. But in my opinion you could do the following to achieve nearly the same thing.
// Some Route
router.get('/foo', function(req, res, next) {
// ...
if(err) {
res.status(500);
return next(err);
}
return res.send('ok');
});
// Another route
router.get('/bar', function(req, res, next) {
// ...
if(badUserId) {
res.status(400);
return next('Invalid userId.');
}
req.result = 'hello';
return next();
});
router.use(function(req, res) {
// I prefer to send the result in the route but an
// approach like this could work
return res.send(req.result);
});
// Error Middleware
router.use(function(err, req, res, next) {
if(res.statusCode === 500) {
// Log the error here
return res.send('Internal server error');
} else {
return res.send(err);
}
});

SailsJs controller context

Does anybody know proper way to pass context to sails controllers action?
Here is my case why I want to do it:
--- AbstractPageController.js ---
module.exports = {
_getPageData: function(req, res) {
return {
data: {
title: this._getTitle(req, res), // if do nothing "this" is global object
menu: this._getMenu(req, res)
}
};
},
_getTitle: function(req, res) { return 'Cool Page'; },
_getMenu: function(req, res) { return [{ href: '/logout' }]; }
};
--- ConcretePageController.js ---
var _ = require('lodash');
_super = require('./AbstractPageController.js');
module.exports = _.merge({}, _super, {
'main': function(req, res) {
res.view('pageTemplate', this._getPageData(req, res));
},
_getTitle: function(req, res) {
return 'Absolutely - ' + _super._getTitle(req, res);
},
_getMenu: function(req, res) {
return [{ href: '/main/'}].concat(_super._getMenu(req, res));
}
});
That's why I need context.
For this particular case I found this solution:
--- routes.js ---
var concreteController = require('../api/controllers/ConcretePageController.js');
module.exports.routes = {
'/concrete_page': function(req, res) { concreteController.main(req, res); }
}
But it seems a little bit ugly and sails hooks (for example policies) stop works.
I was thinking about the other way. The main point of this, is to move all logic to services and to use a simple inheritance. But this seems strange for me too
Any ideas about a better way to reach the cases I have wrote?
P.S. All code I have wrote above is just an example.
I think you whant to pre-set some data to send to view template, right?
is yes you can use one Before hook ( like midleware ) or sails police
I use the sails hook for preload sails features from npm modules in we-plugin https://github.com/wejs/we-plugin
Check this hook for how load user locale in all requests after controllers :
Link: https://github.com/wejs/we-example/blob/master/api/hooks/we-locale/index.js#L15
You should just reference the controller directly
sails.controllers.yourControllerName.getTitle()
https://stackoverflow.com/a/20994036/1821723

LoopBack: cannot call method 'post' of undefined

I am new to loopback and node.js.
I have created two models: Rating and RatingsAggregate
using the loopback explorer, I can query and post against the API just fine.
I am try to setup some basic business logic so I am editing the file Rating.js in common/models
Here is the content of it:
module.exports = function(Rating) {
Rating.afterRemote('**', function(ctx, inst, next) {
var loopback = require('loopback');
var app = loopback();
var ratingsaggregate = app.models.ratingsaggregate;
ratingsaggregate.post({"source":"foobar","restaurantID":"foobar","itemMenuName":"foobar","itemSectionName":"foobar","itemName":"foobar","nRatings1":123,"nRatings2":123,"nRatings3":123,"nRatings4":123,"nRatings5":123,"hasImage":true,"imageSize":123,"latestImageRatingID":"foobar","imageCount":123,"lastUpdated":"foobar"}, function(err, response) {
if (err) console.error(err);
next();
});
});
};
I can load my API, but whenever I run a get statement against it, I get this error:
TypeError: Cannot call method 'post' of undefined
My guess is that somehow ratingsaggregate never gets a value... but I don't know what I am doing wrong. Obviously this is not the end state of my business logic, but I am trying some basic CRUD right now between two models
And... here is the answer. There was a getModel function hidden in the documentation
module.exports = function(Rating) {
Rating.afterRemote('create', function(ctx, inst, next) {
var loopback = require('loopback');
var ratingsaggregate = loopback.getModel('ratingsaggregate');
ratingsaggregate.create({"source":"foobar","restaurantID":"foobar","itemMenuName":"foobar","itemSectionName":"foobar","itemName":"foobar","nRatings1":123,"nRatings2":123,"nRatings3":123,"nRatings4":123,"nRatings5":123,"hasImage":true,"imageSize":123,"latestImageRatingID":"foobar","imageCount":123,"lastUpdated":"foobar"}, function(err, response) {
if (err) console.error(err);
next();
});
});
};
Fixes everything and the behaviour is the expected one

node.js & express: for loop and `app.get()` to serve articles

I'm working on a node js express application that uses app.get() to serve the different web addresses. So app.get('/',{}); serves the home page, app.get('/login'{ }); serves the login page, etc.
Is it good practice to programatically serve pages using a for loop, like in this example?
var a = ["a", "b", "c"];
a.forEach(function(leg) {
app.get('/' + leg, function(req, res){
res.render('index.ejs', {
word : leg
});
});
});
index.ejs is just
<p><%= word %> </p>
So that site.com/a, site.com/b, and site.com/c are all webpages.
I want to utilize this to run .foreach() on a list of all of the article titles (coming from a database of stored articles).
EDIT: The website allows users to submit posts, which become "articles" in a database. I want this loop to route to new submitted articles after they've been posted. If I want to do app.get('/' + newPageName); for user submitted pages AFTER I've already started the server with node server.js, how is that achieved?
Make use of middlewares to better handle the requests. I assume you will have tens/hundreds of posts, adding routes for them like what you've done, is not so elegant.
Consider the following code; I am defining routes of /posts/:legId form. We will match all requests to this route, fetch the article and render it.
If there are handful of routes to be defined you could make use of regular expression to define them.
// dummy legs
var legs = {
a: 'iMac',
b: 'iPhone',
c: 'iPad'
};
app.get('/posts/:leg', fetch, render, errors);
function fetch(req, res, next) {
var legId = req.params.leg;
// add code to fetch articles here
var err;
if (!legs[legId]) {
err = new Error('no such article: ' + legId);
}
req.leg = legs[legId];
next(err);
}
function render(req, res, next) {
res.locals.word = req.leg;
res.render('index');
}
function errors(err, req, res, next) {
console.log(err);
res.locals.error = err.message;
// render an error/404 page
res.render('error');
}
Hope this helps, feel free to ask me any questions you may have.
No, you should not be generating route handlers like that. This is what route parameters are for.
When you start a path component (folder) with a : in an Express route, Express will match any URL that follows the pattern automatically, and place the actual value in req.params. For example:
app.get('/posts/:leg', function(req, res, next) {
// This will match any URL of the form /post/something
// -- but NOT /post/something/else
if (/* the value of req.params.leg is valid */) {
res.render('index.ejs', { word: req.params.leg });
} else {
next(); // Since the user requested a post we don't know about, don't do
// anything -- just pass the request off to the next handler
// function. If no handler responds to the request, Express
// defaults to sending a 404.
}
});
In the real world, you'd probably determine if the leg param is valid by doing a database lookup, which entails making an async call:
app.get('/posts/:leg', function(req, res, next) {
db.query(..., req.params.leg, function(err, result) {
if (err) next(err); // Something went wrong with the database, so we pass
// the error up the chain. By default, Express will
// return a 500 to the user.
else {
if (result) res.render('index.ejs', result);
else next();
}
});
});

How to push out requested data from mongodb in node.js

I'm working with Node.js, express, mongodb, and got stuck on this data passing between frontend and backend.
Note: code below is middleware code for front- and backend communication
Here I successfully get the input value from the frontend by using req.body.nr
exports.find_user_post = function(req, res) {
member = new memberModel();
member.desc = req.body.nr;
console.log(req.body.nr);
member.save(function (err) {
res.render('user.jade', );
});
};
Here is the problem, I need to use the input value I got to find the correct data from my database(mongodb in the backend) and push out to the frontend.
My data structure {desc : ''}, the desc is correspond to the input value so it should look something like this {desc: req.body.nr} which is probably incorrect code here?
exports.user = function(req, res){
memberModel.find({desc: req.body.nr}, function(err, docs){
res.render('user.jade', { members: docs });
});
};
Would love to have some help.
Thanks, in advance!
Have a look at this great tutorial from howtonode.org.
Because as you can see he uses a prototype and a function callback:
in articleprovider-mongodb.js
ArticleProvider.prototype.findAll = function(callback) {
this.getCollection(function(error, article_collection) {
if( error ) callback(error)
else {
article_collection.find().toArray(function(error, results) {
if( error ) callback(error)
else callback(null, results)
});
}
});
};
exports.ArticleProvider = ArticleProvider;
in app.js
app.get('/', function(req, res){
articleProvider.findAll( function(error,docs){
res.render('index.jade', {
locals: {
title: 'Blog',
articles:docs
}
});
})
});
Also make sure you have some error checking from the user input as well as from the anybody sending data to the node.js server.
PS: note that the node, express and mongo driver used in the tutorial are a bit older.

Categories