http://www.meetup.com/Pittsburgh-Node-js/
NICHOLAS MCCLAY
UX Developer

@nickmcclay
MEET YOUR NEIGHBOR
Framework Overview
SAILS HIGHLIGHTS
• Robust Node.js MVC web server framework
!

• Railsy features - scaffolding, DB agnostic ORM
!

• Automatically generated RESTful JSON API
!

• Out-of-the-box real-time socket.io integration
!

• Role based access policies and ACL
!

• JS/CSS asset bundling and minification (Grunt)
QUICK NOTE:
Convention > Configuration = Magic
WHAT IS A
SINGLE PAGE APPLICATION?
TRADITIONAL WEB APPLICATION
http://www.cutekitties.com/kitten/1

One Kitten Please!

HTTP Request
Response
HTML
JavaScript
CSS
TADA! WEB PAGE!
SINGLE PAGE APPLICATION
http://www.cutekitties.com#/kitten/1

One Kitten Please!

HTTP Request
Application

Response
HTML
JavaScript
CSS
/kitten/1?format=json

GET /kitten/1

A JAX Request
[{

"_id" : "abc124efg345",

"lives" : 9,

"curiousity" : "massive",

"cuteness" : 9999,

"eyes" : "blue",

"fur" : "yellow"

}]

Response
JSON
TADA! WEB APP!
Heading out to sea
SAILS VERSIONS
V0.9

Coming

V0.10
V0.10

Soon

• Current NPM default

• beta version (RC3)

• Reliable starting place

• Significant Refactors

• What I’ll be demoing

• API changes

in this presentation

• Associations
• Here be dragons…

https://github.com/balderdashy/sails-docs/blob/master/Migration-Guide.md
DOCUMENTATION SOURCES
Official Website Documentation

http://sailsjs.org/#!documentation

GitHub Documentation Repo
Coming

V0.10
Soon

https://github.com/balderdashy/sails-docs/tree/0.10
CREATE A NEW SAILS PROJECT
http://sailsjs.org/#!getStarted
0.10.0 NPM INSTALL WEIRDNESS

If you encounter this, try:
npm install -g
SAILS CLI BASICS
Turn on asset linking

Make a new app

sails new <appName>
Run App

sails lift

--linker

Set Env Config

Display all logs

--dev --prod --verbose

Display Sails.js Version

sails version
SAILS SCAFFOLDING
Generate Scaffolding

sails generate (type) <id>
model, controller, default is both
Pluggable Generators for Scaffolding
Coming

V0.10
Soon

sails generate (type) <id>
model, controller, api, blog,
user, whatever you want!
MODELS

(Waterline Collection)

api/models

var Person = {

schema : true,

attributes: {

firstName: 'string',

lastName: 'string',

password : {

type : 'string',

required : true,

notEmpty : true

},

age : {

type: 'integer',

max: 150,

required: true

},

birthDate: 'date',

phoneNumber: {

type: 'string',

defaultsTo: '111-222-3333'

},

emailAddress: {

type: 'email', // Email type will get validated by the ORM

required: true

}

}

};

model.exports = Person;
BEYOND ATTRIBUTES
// Define a custom instance method

fullName: function() {

return this.firstName + ' ' + this.lastName;

},







// Lifecycle Callbacks

beforeCreate: function(values, next) {

bcrypt.hash(values.password, 10, function(err, hash) {

if(err) return next(err);

values.password = hash;

next();

});

},

// Override toJSON instance method

// to remove phoneNumber value

toJSON: function() {

var obj = this.toObject();

delete obj.password;

return obj;

}
ORM - WATERLINE

https://github.com/balderdashy/waterline
• ActiveRecord, Hibernate, and
Mongoose
• emphasis on modularity,
testability, and consistency
across adapters
• Waterline Adapter -> DB Query
• Custom Adapter methods
Coming

ASSOCIATIONS

V0.10

Associate models across different data stores
Defining Association
var Blog = {

attributes : {

title : 'string',

body : 'string',

author : {

model : 'person'

}

}

};

Soon

Relationship Types
• One way
• One-to-One
• One-to-Many
• Many-to-Many

Blog.find({title:'Sails.js Presentation'}).populate('author').exec(console.log);
Full Sail Ahead!
RESOURCEFUL ROUTING
CRUD Routes

get /:controller/:id?	

post /:controller	

put /:controller/:id	

delete /:controller/:id

FREE
To disable set the ‘shortcuts’ flag to
false in config/controllers.js,

REST Routes

/:controller/find/:id?	

/:controller/create	

/:controller/update/:id	

/:controller/destroy/:id

FREE
!

To disable set the ‘rest’ flag to false in
config/controllers.js
CONTROLLERS
api/controllers

var ChickenController = {

// Peck the chicken specified by id (subtract 50 HP)

peck: function (req,res) {

Chicken.find(req.param('id')).exec(function (err, chicken) {

if (err) return res.send(err,500);

if (!chicken) return res.send("No other chicken with that id exists!", 404);

if (chicken.hp <= 0) return res.send("The other chicken is already dead!", 403);

chicken.hp -= 50; // Subtract 50 HP from the chicken



chicken.save(function (err) { // Persist the change

if (err) return res.send(err,500);

// Report back with the new state of the chicken

res.json(chicken);

});

});

}

};

module.exports = ChickenController;
SOCKET.IO

http://socket.io/

// all controller routes return valid responses

socket.get('/todo/count', function(results) { console.log(results) });




// create a new item

socket.post('/todo', {"title" : "this is from sockets"}, function(err,results)
{ console.log(err,results) });




// all valid ways to update with websockets

socket.put('/todo', {'id' : 1, 'title' : "updated1"}, function(results) { console.log(results) });

socket.put('/todo/1', {'title' : "updated2"}, function(results) { console.log(results) });

socket.get('/todo/update/1', {'title' : "updated3"}, function(results) { console.log(results) });




// all valid ways to delete with websockets

socket.delete('/todo', {'id' : 1}, function(results) { console.log(results) });

socket.delete('/todo/1', function(results) { console.log(results) });

socket.get('/todo/delete/21',function(results) { console.log(results)});




// listen for messages

socket.on('message', function(message) { console.log(message) });




// listen for messages from a specific controller

socket.on('todo',function(message) { console.log(message) });

Coming

V0.10
Soon
POLICIES
api/policies/isAuthenticated.js
module.exports = function(req, res, next) {

// User is allowed, proceed to the next policy, 

// or if this is the last policy, the controller

if (req.session.authorized == true) {

return next();

}




// User is not allowed

// (default res.forbidden() behavior can be overridden in `config/403.js`)

return res.forbidden('You are not permitted to perform this action.');

};

config/policies.js
module.exports.policies = {

// Default policy for all controllers and actions

// (`true` allows public access) 

'*': true,




// Policies for /Foo/* routes

FooController: {

"*" : true,

'update' : 'isAuthenticated',

'destroy' : ['isAuthenticated','isOwner']

}

};
ASSET MANAGEMENT
assets/linker/

Enabling Asset Linking

sails new <appName>

--linker

Place Assets in between special linker flags
GET STARTED BUILDING
https://github.com/cgmartin/sailsjs-angularjs-bootstrap-example

https://www.npmjs.org/package/generator-sails

http://irlnathan.github.io/sailscasts/
THANKS!

@nickmcclay

Intro to Sail.js

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
    SAILS HIGHLIGHTS • RobustNode.js MVC web server framework ! • Railsy features - scaffolding, DB agnostic ORM ! • Automatically generated RESTful JSON API ! • Out-of-the-box real-time socket.io integration ! • Role based access policies and ACL ! • JS/CSS asset bundling and minification (Grunt)
  • 6.
    QUICK NOTE: Convention >Configuration = Magic
  • 7.
    WHAT IS A SINGLEPAGE APPLICATION?
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
    [{
 "_id" : "abc124efg345",
 "lives": 9,
 "curiousity" : "massive",
 "cuteness" : 9999,
 "eyes" : "blue",
 "fur" : "yellow"
 }] Response JSON
  • 17.
  • 18.
  • 19.
    SAILS VERSIONS V0.9 Coming V0.10 V0.10 Soon • CurrentNPM default • beta version (RC3) • Reliable starting place • Significant Refactors • What I’ll be demoing • API changes in this presentation • Associations • Here be dragons… https://github.com/balderdashy/sails-docs/blob/master/Migration-Guide.md
  • 20.
    DOCUMENTATION SOURCES Official WebsiteDocumentation http://sailsjs.org/#!documentation GitHub Documentation Repo Coming V0.10 Soon https://github.com/balderdashy/sails-docs/tree/0.10
  • 21.
    CREATE A NEWSAILS PROJECT http://sailsjs.org/#!getStarted
  • 22.
    0.10.0 NPM INSTALLWEIRDNESS If you encounter this, try: npm install -g
  • 23.
    SAILS CLI BASICS Turnon asset linking Make a new app sails new <appName> Run App sails lift --linker Set Env Config Display all logs --dev --prod --verbose Display Sails.js Version sails version
  • 24.
    SAILS SCAFFOLDING Generate Scaffolding sailsgenerate (type) <id> model, controller, default is both Pluggable Generators for Scaffolding Coming V0.10 Soon sails generate (type) <id> model, controller, api, blog, user, whatever you want!
  • 25.
    MODELS (Waterline Collection) api/models var Person= {
 schema : true,
 attributes: {
 firstName: 'string',
 lastName: 'string',
 password : {
 type : 'string',
 required : true,
 notEmpty : true
 },
 age : {
 type: 'integer',
 max: 150,
 required: true
 },
 birthDate: 'date',
 phoneNumber: {
 type: 'string',
 defaultsTo: '111-222-3333'
 },
 emailAddress: {
 type: 'email', // Email type will get validated by the ORM
 required: true
 }
 }
 };
 model.exports = Person;
  • 26.
    BEYOND ATTRIBUTES // Definea custom instance method
 fullName: function() {
 return this.firstName + ' ' + this.lastName;
 },
 
 
 // Lifecycle Callbacks
 beforeCreate: function(values, next) {
 bcrypt.hash(values.password, 10, function(err, hash) {
 if(err) return next(err);
 values.password = hash;
 next();
 });
 },
 // Override toJSON instance method
 // to remove phoneNumber value
 toJSON: function() {
 var obj = this.toObject();
 delete obj.password;
 return obj;
 }
  • 27.
    ORM - WATERLINE https://github.com/balderdashy/waterline •ActiveRecord, Hibernate, and Mongoose • emphasis on modularity, testability, and consistency across adapters • Waterline Adapter -> DB Query • Custom Adapter methods
  • 28.
    Coming ASSOCIATIONS V0.10 Associate models acrossdifferent data stores Defining Association var Blog = {
 attributes : {
 title : 'string',
 body : 'string',
 author : {
 model : 'person'
 }
 }
 }; Soon Relationship Types • One way • One-to-One • One-to-Many • Many-to-Many Blog.find({title:'Sails.js Presentation'}).populate('author').exec(console.log);
  • 29.
  • 30.
    RESOURCEFUL ROUTING CRUD Routes get/:controller/:id? post /:controller put /:controller/:id delete /:controller/:id FREE To disable set the ‘shortcuts’ flag to false in config/controllers.js, REST Routes /:controller/find/:id? /:controller/create /:controller/update/:id /:controller/destroy/:id FREE ! To disable set the ‘rest’ flag to false in config/controllers.js
  • 31.
    CONTROLLERS api/controllers var ChickenController ={
 // Peck the chicken specified by id (subtract 50 HP)
 peck: function (req,res) {
 Chicken.find(req.param('id')).exec(function (err, chicken) {
 if (err) return res.send(err,500);
 if (!chicken) return res.send("No other chicken with that id exists!", 404);
 if (chicken.hp <= 0) return res.send("The other chicken is already dead!", 403);
 chicken.hp -= 50; // Subtract 50 HP from the chicken
 
 chicken.save(function (err) { // Persist the change
 if (err) return res.send(err,500);
 // Report back with the new state of the chicken
 res.json(chicken);
 });
 });
 }
 };
 module.exports = ChickenController;
  • 32.
    SOCKET.IO http://socket.io/ // all controllerroutes return valid responses
 socket.get('/todo/count', function(results) { console.log(results) });
 
 // create a new item
 socket.post('/todo', {"title" : "this is from sockets"}, function(err,results) { console.log(err,results) });
 
 // all valid ways to update with websockets
 socket.put('/todo', {'id' : 1, 'title' : "updated1"}, function(results) { console.log(results) });
 socket.put('/todo/1', {'title' : "updated2"}, function(results) { console.log(results) });
 socket.get('/todo/update/1', {'title' : "updated3"}, function(results) { console.log(results) });
 
 // all valid ways to delete with websockets
 socket.delete('/todo', {'id' : 1}, function(results) { console.log(results) });
 socket.delete('/todo/1', function(results) { console.log(results) });
 socket.get('/todo/delete/21',function(results) { console.log(results)});
 
 // listen for messages
 socket.on('message', function(message) { console.log(message) });
 
 // listen for messages from a specific controller
 socket.on('todo',function(message) { console.log(message) }); Coming V0.10 Soon
  • 33.
    POLICIES api/policies/isAuthenticated.js module.exports = function(req,res, next) {
 // User is allowed, proceed to the next policy, 
 // or if this is the last policy, the controller
 if (req.session.authorized == true) {
 return next();
 }
 
 // User is not allowed
 // (default res.forbidden() behavior can be overridden in `config/403.js`)
 return res.forbidden('You are not permitted to perform this action.');
 }; config/policies.js module.exports.policies = {
 // Default policy for all controllers and actions
 // (`true` allows public access) 
 '*': true,
 
 // Policies for /Foo/* routes
 FooController: {
 "*" : true,
 'update' : 'isAuthenticated',
 'destroy' : ['isAuthenticated','isOwner']
 }
 };
  • 34.
    ASSET MANAGEMENT assets/linker/ Enabling AssetLinking sails new <appName> --linker Place Assets in between special linker flags
  • 35.
  • 36.