How to achieve MVC pattern in sails? - node.js

I'm wondering what is the best way to achieve the MVC pattern is sails.js.
Now I have the following structure: I have a route which redirects the request to the controller:
'POST /api/user/...': {controller: 'UserController', action: 'someFunction'},
My user model:
module.exports = {
...
attributes: {...}
}
I have the controller:
someFunction: function(req, res) {
let param = req.body.param;
let userId = req.session.userId;
userService.someFunction(userId, param, function (result) {
return res.json({result});
});
},
And in the userService I have methods for manipulating the database, for example:
someFunction: function(userId, param, callback){
User.findOne(userId).exec(function (err, user){
if (err) {
callback(false);
} else {
// find the user's additional info
User.update({name: param}, { ... })
}
My real question is that is this a good pattern to follow or I'm on the wrong path.
Thanks for any kind of response.

Note: Answer to this question can be opinion-based. Here is my opinion.
Sails project is scaffolded in MVC pattern. There are separate folders for Models (api/models), Controllers (api/controllers), and Views (views).
You're doing right thing having database methods in Services.
Anything that can be required by more than one controller action should go into services.
Other thoughts:
Any logic which deals with a single model should be in that Model
Prefer to apply constraints, foreign keys at database level as well
With Node.js v7.6+, you can use async/await. Sails (Waterline) methods return Bluebird promises which works well with async/await.

Related

Sharing DB queries in Node.js methods

What's the best practice for sharing DB query code between multiple Node.js Express controller methods? I’ve searched but the samples I’ve found don’t really get into this.
For example, I have this getUser method (using Knex for MySQL) that makes a call to get user info. I want to use it in other methods but I don't need all the surrounding stuff like the response object.
export let getUser = (req: Request, res: Response, next: NextFunction) => {
try {
knex.select().where('email', req.params.email)
.table('users')
.then( (dbResults) => {
const results: IUser = dbResults[0];
res
.status(200)
.set({ 'Content-Type': 'application/json', 'Connection': 'close' })
.send(results);
});
} catch (err) {
res.send({ error: "Error getting person" + req.params.email });
return next(err);
}
};
It seems wrong to repeat the query code somewhere else where I need to get the user. Should I turn my DB query code into async functions like this example and then call them from within the controller methods that use the query? Is there a simpler way?
/**
* #param {string} email
*/
async function getUserId(email: string) {
try {
return await knex.select('id')
.where('email', email)
.table('users');
} catch (err) {
return err;
}
}
You can for example create "service" modules, which contain helpers for certain type of queries. Or you could use ORM and implement special queries in each model that is called "fat model" design. Pretty much anything goes as long as you remember to not create new knex instance in every helper module, but you just pass knex (containing its connection pool) for the helper methods so that all queries will share the same connection pool.
ORM's like objection.js also provides a way to extend query builder API so you can inherit custom query builder with any special query helper that you need.

Should my controller take the (res, req) params or already extracted params?

I wonder what way should I organize my routing in expressJS :
Params parsing in Controller
router.get('/users/:id', UserController.get);
class UserController {
get(res, req) {
var id = res.params.id;
UserModel.get(id, function(user) {
res.send(user);
}
}
}
Params parsing in Route
router.get('/users/:id', function(req, res) {
var id = req.params.id;
UserController.get(id, function(user) {
res.json(user);
}
});
class UserController {
get(id, fn) {
UserModel.get(id, fn);
}
}
I find the second version Params parsing in Route easier for
unit test
In case of change in the URL params or request body
but most of the example I found use the first version, why ?
If you consider a much larger, messier real world application, with route names that no longer match controller names etc., it might be beneficial to place the full routing table (all of the router.xxx calls) in one place, such as a routes.js. For a given url, this makes it much simpler for a new developer to figure out which code handles which url.
If you included all of the parameter parsing in your routes.js, it would become really messy and you'd likely lose some of the benefit of having collected all that into one file in the first place.
That said, there's no reason why you cant have the best of both worlds by separating the routing, the parameter parsing/response formatting, and the controller logic each into their own modules.

MEAN stack: Wondering api.js and crud.js

I'm studying MEAN stack these day, So I make some sample apps following guidance. I made up "Bookshelf" application just few hours ago, this is provided by google cloud service, so I should delve into sample code to understand how it works.
Whole source code : https://github.com/GoogleCloudPlatform/nodejs-getting-started/tree/master/2-structured-data
Sample application : http://mymongo-1165.appspot.com/books
books/api.js
router.get('/', function list(req, res) {
model.list(10, req.query.pageToken,
function(err, entities, cursor) {
if (err) { return handleRpcError(err, res); }
res.json({
items: entities,
nextPageToken: cursor
});
});
});
books/curd.js
router.get('/', function list(req, res) {
model.list(10, req.query.pageToken,
function(err, entities, cursor) {
if (err) { return handleRpcError(err, res); }
res.render('books/list.jade', {
books: entities,
nextPageToken: cursor
});
}
);
});
these 2 codes are similar, but I don't know why these similar codes comes up. I think crud.js enough, but why api.js comes up. Could you explain how these 2 codes work?
In this sample application, there are two interface:
graphic user interface (GUI) - curd.js handles generating HTML that is rendered later in the browser (in our case jade tempting language is involved)
application programming interface (API) - api.js provides the way to interact with application programmatically, without browser (ex: create new record in database, or query some data by making specific call to particular route)
For deeper understanding I would suggest learning more about express.js, that will give better idea what those outputs are.
P.S. Welcome to MEAN world :)

How to use params to filter database objects with Sailsjs

I'm still a bit new to Node in general, so I'm sorry if this is noob question. My setup is Sailsjs + MongoDB.
I have a RESTful API controller set up to handle the "lab" collection in my DB.
Here is what I use in my controller to pull up all the objects in this collection when /lab/ is used:
index: function (req, res, next) {
Lab.find(function foundLabs(err, labs) {
if (err) return next(err);
res.view({
labs: labs
});
});
},
In this collection there are fields for "site" and "lab" and I'd like to be able to filter what shows up with params like:
/lab/:site
/lab/:site/:lab
So if "/lab/aq" was pulled up it would get all objects in the AQ site and if "/lab/aq/123" was pulled up it would get the objects for the 123 lab in the AQ site.
I know this would likely be done with the Lab.find function, but I haven't been able to find any documentation which gives me an answer I'm looking for.
Any help would be appreciated.
In your config/routes.js file you need to add a route with optional parameters:
'/findLabs/:site?/:lab?': 'LabController.findLabs'
// use a different route than 'lab' to avoid interfering with the blueprint routes
Then, in your LabController.js, if the requested url had site and/or lab, you will find them in req.params:
// when a request is sent to '/findLabs/aq/123':
findLabs: function(req, res, next) {
sails.log(req.params) // {site: 'aq', lab: '123'}
// you can use them to filter:
var query = Lab.find();
if (req.params.site) query.where({site: req.params.site});
if (req.params.lab) query.where({lab: req.params.lab});
query.exec(function(err, labs) {
if (err) return res.serverError();
res.json(labs);
});
}

Angular UI Client Side Pagination

I would like to enable pagination and I'm torn between client side and server side pagination. In the long term (more data) it is probably better to do server side pagination, but I haven't found a good tutorial on it.
I use Angular/Express/Mongo. I have the Boostrap UI in use, and would like to use their pagination directive for pagination. I have read some articels on how to kind of do it, but they are outdated and I cannot get it to work. http://fdietz.github.io/recipes-with-angular-js/common-user-interface-patterns/paginating-through-client-side-data.html
Could anybody help me get that example to work with Bootstrap UI for Angular?
If you have a set number of items per page, you could do it this way :
Define an angular service to query the data on your server.
.factory('YourPaginationService', ['$resource',
function($resource) {
return $resource('baseUrl/page/:pageNo', {
pageNo: '#pageNo'
});
}
]);
Call it via the angular controller. Don't forget to inject your service, either globally or in the controller.
$scope.paginationController = function($scope, YourPaginationService) {
$scope.currentPage = 1;
$scope.setPage = function (pageNo) {
$scope.currentPage = pageNo;
YourPaginationService.query({
pageNo: '$scope.currentPage'
});
};
};
On express 4 (if you have it), set up your route.
app.route('/articles/page/:pageNo')
.get(data.listWithPagination) //random function name
Then you need to wire that function with the desired Mongo request in your Node controller. If you have Mongoose, it works like this :
exports.listWithPagination = function(req, res) {
var pageLimit = x; //Your hardcoded page limit
var skipValue = req.params.pageNo*pageLimit;
YourModel.find() //Your Mongoose model here, if you use Mongoose.
.skip(skipValue)
.limit(pageLimit)
.exec(function(err, data) {
if (err) {
return res.send(400, {
message: getErrorMessage(err)
});
} else {
res.jsonp(data);
}
});
};
That's how I would do it on a typical MEAN stack. If you're working with different libraries/technologies, you might need to adapt a few things.

Resources