This document discusses best practices for developing Node.js applications. It recommends using frameworks like Express for building web apps, libraries like Async to avoid callback hell, and organizing code into modular sub-applications. It also covers testing, error handling, documentation, and open-sourcing projects. Standards like Felix's Style Guide and domain-driven design principles are advocated. Communication channels like events, HTTP APIs, and WebSockets are examined.
5. Organisation du code
Objectif: éviter le code spaghetti
Utiliser un framework pour les applis web
visionmedia/express (ou viatropos/tower)
Eviter la course aux callbacks
caolan/async (ou kriskowal/q)
Inclure des routes et monter des sous-applications
«Fat model, Skinny controller»
Modules de service pour éviter un modèle trop fat
6. // main app.js
var express = require('express');
var app = module.exports = express.createServer();
app.configure(function(){
app.use(app.router);
// these middlewares are required by some of the mounted apps
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({ secret: 'qdfegfskqdjfhskjdfh' }));
});
// Routes
app.use('/api', require('./app/api/app'));
app.use('/dashboard', require('./app/dashboard/app'));
app.get('/', function(reaq, res) {
res.redirect('/dashboard/events');
});
app.listen(3000);
15. Canaux de communication
Dans un module
Entre applications Client / Serveur
ou une application
Appels de
Notifications Events socket.io
méthodes
Echanges de Appels de XMLHTTP
API HTTP
données méthode Request
Traitements
Promise AMQP Mentir*
asynchrones
16. // server-side
var socketIo = require('socket.io');
var CheckEvent = require('./models/checkEvent');
var io = socketIo.listen(app);
CheckEvent.on('postInsert', function(event) {
io.sockets.emit('CheckEvent', event.toJSON());
});
18. Gestions des erreurs
try {} catch {} ne marche pas en asynchrone
function (err, results) est la signature
standard des callbacks asynchrones
"Leave early"
Utiliser les codes d’erreur HTTP pour les APIs
19. app.delete('/check/:id', function(req, res, next) {
Check.findOne({ _id: req.params.id }, function(err, check) {
if (err) {
return next(err);
}
if (!check) {
return next(new Error('No check with id ' + req.params.id));
}
check.remove(function(err2){
if (err2) {
req.flash('error', 'Error - Check not deleted');
res.redirect('/checks');
return;
}
req.flash('info', 'Check has been deleted');
res.redirect('/checks');
});
});
});
20. Tests Unitaires
Librairies (presque) standard
Assertions : visionmedia/should.js (ou node/assert)
TDD : visionmedia/mocha (ou caolan/node-unit)
BDD : visionmedia/mocha (ou mhevery/jasmine-node)
L’asynchrone se teste aussi très bien
Pas besoin de mocker quand on peut monkey-patcher
21. describe('Connection', function(){
var db = new Connection
, tobi = new User('tobi')
, loki = new User('loki')
, jane = new User('jane');
beforeEach(function(done){
db.clear(function(err){
if (err) return done(err);
db.save([tobi, loki, jane], done);
});
})
describe('#find()', function(){
it('respond with matching records', function(done){
db.find({ type: 'User' }, function(err, res){
if (err) return done(err);
res.should.have.length(3);
done();
})
})
})
})
23. Projet open-source
Configurable
app.configure() pas compatible avec un SCM
lorenwest/node-config (ou flatiron/nconf)
Extensible
Custom events
Plugin architecture
24. // in main app.js
path.exists('./plugins/index.js', function(exists) {
if (exists) {
require('./plugins').init(app, io, config);
};
});
// in plugins/index.js
exports.init = function(app, io, config) {
require('./console').init();
}
25. module.exports = exports = function lifecycleEventsPlugin(schema) {
schema.pre('save', function (next) {
var model = this.model(this.constructor.modelName);
model.emit('preSave', this);
this.isNew ? model.emit('preInsert', this) :
model.emit('preUpdate', this);
this._isNew_internal = this.isNew;
next();
});
schema.post('save', function() {
var model = this.model(this.constructor.modelName);
model.emit('postSave', this);
this._isNew_internal ? model.emit('postInsert', this) :
model.emit('postUpdate', this);
this._isNew_internal = undefined;
});
schema.pre('remove', function (next) {
this.model(this.constructor.modelName).emit('preRemove', this);
next();
});
schema.post('remove', function() {
this.model(this.constructor.modelName).emit('postRemove', this);
});
};
26. Questions ?
François Zaninotto
@francoisz http://github.com/fzaninotto