38
Developing web-apps like it’s 2013 a case-study of using node.js to build entreprise applications 1

Developing web-apps like it's 2013

Embed Size (px)

DESCRIPTION

A case-study of using node.js to develop entreprise applications. Includes real-world examples for express.js, mongoose.js and async.js

Citation preview

Page 1: Developing web-apps like it's 2013

1

Developing web-apps like it’s 2013

a case-study of using node.js to build entreprise applications

Page 2: Developing web-apps like it's 2013

2

Who?

Laurent Van Basselaere@Laurent_VB

I do stuff with codeat Arhs Developments

Page 3: Developing web-apps like it's 2013

3

ARHS Developments

10 years, 300 peopleConsulting & fixed price projectsSoftware (Java)Business Intelligence

Page 4: Developing web-apps like it's 2013

4

testing

Page 5: Developing web-apps like it's 2013

5

Testing

Page 6: Developing web-apps like it's 2013

6

Page 7: Developing web-apps like it's 2013

7

MS Access? SRSLY?

MS AccessMultiple copiesSingle userNo remote useLame

Page 8: Developing web-apps like it's 2013

8

Fatman FTW

MS Access BrowserMultiple versions CentralizedSingle user Unlimited usersNo remote use EverywhereLame Awesome

Page 9: Developing web-apps like it's 2013

9

We wrote a web-app

Page 10: Developing web-apps like it's 2013

10

Fatman tech stack

BootstrapKnockoutExpressNodeMongooseMongoDB

asyncunderscoremoment

Page 11: Developing web-apps like it's 2013

11

Elegant MongoDB object modeling for Node.js

ORM seemed desirableClean object model to use in app code

Page 12: Developing web-apps like it's 2013

var mongoose = require(‘mongoose’);mongoose.connect(‘localhost’, ‘fatman’);

var ProjectSchema = new mongoose.Schema({ id : String , name : String , users : [String]});

var Project = mongoose.model('Project', ProjectSchema);

Schema

12

Page 13: Developing web-apps like it's 2013

var project = new Project({ id: ‘AKCT’ , name: ‘GOCA Newsoft AKCT’ , users: [‘vanbasla’, ‘grosjech’]});

project.save(function (err){ //… callback after save});

CREATE/EDIT

13

Page 14: Developing web-apps like it's 2013

// find project by idProject.where(‘id’, ‘AKCT’) .findOne(function(err, project) { // do something with search result });

// find my projectsProject.find({‘users’: username}) .exec(function(err, projects){ // do something with search results });

RETRIEVE

14

Page 15: Developing web-apps like it's 2013

function trim(value){ return value ? value.trim() : value;}function lessThan80chars(value){ return value.length <= 80;}

var ProjectSchema = new mongoose.Schema({ id : {type: String, required: true, unique: true} , name : {type: String, set: trim, validate: [ lessThan80chars, 'Value too long. Max 80 characters.']}});

MORE Schema

15

Page 16: Developing web-apps like it's 2013

// staticsProjectSchema.statics.findById = function(projectId, cb){ Project.where('id', projectId).findOne(cb);};

// methodsProjectSchema.methods.issueTrackerEnabled = function() { return this.issueTracker != null;};

// middlewareProjectCaseSchema.pre(‘remove’, function(next) { // do something when a Project is deleted next();});

Advanced

16

Page 17: Developing web-apps like it's 2013

17

In fatman

We use setters+ pre-save middleware

to keep history of edits.

Page 18: Developing web-apps like it's 2013

// creates a setter for fieldfunction setter(field) { return function setField(newValue) { this._oldValues = this._oldValues || {}; this._oldValues[field] = this[field]; return newValue; }}

var TestCaseSchema = new Schema({ id: {type:Number,index:true}, description: {type:String, set: setter('description')}, history: [Schema.Types.Mixed]});

In fatman

18

Page 19: Developing web-apps like it's 2013

// Populate history before save.TestCaseSchema.pre('save', function (next) { var self = this , oldValues = this._oldValues || {};

delete this._oldValues;

this.modifiedPaths.forEach(function (field) { if (field in oldValues) { self.history.push({ ‘old': oldValues[field], ‘new’: self[field] }); } });

next();});

In fatman

19

Page 20: Developing web-apps like it's 2013

Express is a minimal and flexible node.js web application framework.

Simple and modularNode de-facto standard

Page 21: Developing web-apps like it's 2013

var express = require('express'), consolidate = require('consolidate');

// create an express appvar app = express();

// configure view engineapp.engine('html', consolidate.handlebars);app.set('views', __dirname + '/views');

// configure a route with an url parameterapp.get('/hello/:name', hello);

function hello(req, res, next){ res.render('hello.html', { 'name' : req.params.name });}

app.listen(1337);console.log('Listening on port 1337');

Hello Express

Page 22: Developing web-apps like it's 2013

function list (req, res, next){ Project.findById(req.params.projectId, function(err, project){ if (err) return next(err); TestCase.find({‘project’: project}, function(err, testcases){ if (err) return next(err); res.render(‘testcases.html’, { ‘project’: project, ‘testcases’: testcases }); }); });}

controller

Page 23: Developing web-apps like it's 2013

function show (req, res, next){ Project.findById(req.params.projectId, function(err, project){ if (err) return next(err); TestCase.findOne({‘project’:project, ‘id’:id}, function(err, tc){ if (err) return next(err); res.render(‘testcase.html’, { ‘project’: project, ‘testcase’: tc }); }); });}

controller

Page 24: Developing web-apps like it's 2013

function save (req, res, next){ Project.findById(req.params.projectId, function(err, project){ if (err) return next(err); var tc = new TestCase(req.body); tc.project = project; tc.save(function(err, tc){ if (err) return next(err); // redirect after post res.redirect(req.url); }); });}

controller

Page 25: Developing web-apps like it's 2013

function loadProject(req, res, next){ Project.findById(req.params.projectId, function(err, project){ if (err) return next(err);

res.locals.project = project; next(); });}

// before all routes requiring a projectapp.all('/:projectId/*', loadProject);

MIDDLEWARE

Page 26: Developing web-apps like it's 2013

function list (req, res, next){ var project = res.locals.project; TestCase.find({‘project’:project}, function(err, testcases){ if (err) return next(err); res.render(‘testcases.html’, { ‘testcases’: testcases }); });}

BETTER

Page 27: Developing web-apps like it's 2013

function show (req, res, next){ var project = res.locals.project; TestCase.findOne({‘project’:project, ‘id’:id}, function(err, tc){ if (err) return next(err);

res.render(‘testcase.html’, { ‘testcase’: tc }); });}

BETTer

Page 28: Developing web-apps like it's 2013

function save (req, res, next){ var tc = new TestCase(req.body); tc.project = res.locals.project; tc.save(function(err){ if (err) return next(err); res.redirect(req.url); });}

BETTer

Page 29: Developing web-apps like it's 2013

function search (req, res, next){ Project.findById(projectId, function(err, project){ if (err) return next(err);

TestPlan.findByIdentifier(project, testPlanId, function(err, testPlan) { if (err) return next(err);

var tags = getTagsFromRequest(req); TestCase.findByTag(testPlan, tags, function(err, tagQuery, testCases){ if (err) return next(err);

TestCase.countTags(tagQuery, function(err, tagsResult) { if (err) return next(err);

res.render(‘search’, { ‘testCases’ : testCases, ‘tagsResult’ : tagsResult, ‘project’ : project, ‘testPlan’ : testPlan, ‘tags’ : tags }); }); }); }); });}

pyramid of doom

Page 30: Developing web-apps like it's 2013

Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript

Async.js

Page 31: Developing web-apps like it's 2013

function search (req, res, next){ async.waterfall([ function(cb){ Project.findById(projectId, cb); }, function(cb, project){ res.locals.project = project; TestPlan.findByIdentifier(project, testPlanId, cb); }, function(cb, testPlan){ res.locals.testPlan = testPlan; var tags = res.locals.tags = getTagsFromRequest(req); TestCase.findByTag(testPlan, tags, cb); }, function(cb, tagQuery, testCases){ res.locals.testCases = testCases; TestCase.countTags(tagQuery, cb); } ], function(err, tagsResult){ if (err) return next(err); res.render(‘search’, tagsResult); });}

pyramid NO MOREAsync.js

Page 32: Developing web-apps like it's 2013

var ids = [‘AKCT’, ‘FATMAN’];var projects = [];

ids.forEach(function(id){ Project.findById(id, function(err, project){ projects.push(project); });});res.render(‘projects’, {‘project’ : projects});

MORE async Async.js

WRONG

Page 33: Developing web-apps like it's 2013

var ids = [‘AKCT’, ‘FATMAN’];var projects = [];

async.each(ids, function(id, next){ Project.findById(id, function(err, project){ projects.push(project); next(); })}, function(err){ res.render(‘projects’, {‘projects’: projects});});

MORE async Async.js

Page 34: Developing web-apps like it's 2013

Collections

eachmapfilterrejectreducedetectsortBy…

MORE async Async.js

Control flow

seriesparallelwhilstdoWhilstuntildoUntilwaterfall…

Page 35: Developing web-apps like it's 2013

FATMAN

60 days dev6 months prod12 projects (2 to 10 users)

Page 36: Developing web-apps like it's 2013

FATMAN

Page 37: Developing web-apps like it's 2013

FATMAN

Page 38: Developing web-apps like it's 2013

FATMAN