KEMBAR78
OSCON 2011 - Node.js Tutorial | KEY
Node.js Workshop



Tom Hughes-Croucher
Chief Evangelist / Node Tech Lead
@sh1mmer
tom@joyent.com
Overview
•   Introduction
•   Why Server-Side JavaScript?
•   What is Node?
•   Using Node
•   Understanding Node
•   Node Ecosystem
•   Programming Style
•   More Complex applications
•   Deploying Node apps to the cloud
Introduction

• Tom Hughes-Croucher
• Chief Evangelist at Joyent
• Node.js core contributor
• Author of "Up and Running with Node.js"
Scalable Server-Side Code with JavaScript




Node                     Up and Running




                        Tom Hughes-Croucher
Major update
 this week
Why Server-Side
  JavaScript?
JavaScript
programmers
    3>2>1
Massive Code base of
 jQuery and other JS
      libraries
Laziness or “I’m sick
of writing stuff twice”
I could have said efficiency, but I think we all
secretly long to sit around in our underwear.
Progressive
Enhancement is free*
Remember WWCD (What Would Crockford Do)




                                    *close enough
TL;DR:
SSJS is Awesome
 Like a Unicorn riding a Narwhal
If SSJS is so
   awesome
why is it "new"?
1. Professionalism
“Yahoo!'s corporate
 motto is: Don't be
      eval().“
2. JavaScript
 Runtimes
Runtimes

• V8 (Google), C++
• Spider Monkey (Mozilla), C++
• Rhino (Mozilla), Java
V8




                   Spider
                   Monkey

JavaScript Performance
8x




Sep 08!   Mar 11!
Anatomy of SSJS
Node
       {
           !
Runtime != Browser
No DOM
(By default, anyway)
Summary
• Benefits of SSJS
 • Lots of JavaScript expertise
 • Lots of web code in JS libraries
 • Write once, run anywhere
 • Progressive Enhancement
• Why SSJS happened now
 • Professionalism in JavaScript
 • New generation of JavaScript runtimes
What is Node?
Node
• JavaScript programming environment
• Uses V8 runtime
• Event Driven
• Non-blocking libraries
• Supports CommonJS module format
• Supports C/C++ based add-ons
Woah! Overload.
1. It's JavaScript
See Above.
2. It's Fast
concurrency=300, Smaller is Better
                 400




                 300
response time (ms)




                                                                          server
                                                                             nginx
                 200                                                         thin
                                                                             tornado
                                                                             node_buffer


                 100




                       24      26    28    210     212      214   216   218
                                    response size (bytes)
3. It's easy to extend
'Modules' in JS
'Add-ons' in C
4. Node is _not_ Rails/
     Django/etc
Node is bare-bone to
     the metal
However, the Node
community are making
  Rails/Django/etc
5. Node is young
Stable is "stable"
Unstable moves fast
Using Node
Using Node

• Part 1. Installation
• Part 2. Basics
• Part 3. Getting stuck in
Part 1. Installation
Nave
a.k.a the easy way
Enki:~ $ wget -q http://github.com/isaacs/nave/raw/
master/nave.sh
Enki:~ $ chmod 755 nave.sh
Enki:~ $ ./nave.sh install latest
Nave

• Installs and versions Node
• Allows Node shells with specific versions
• Allows you to get 'latest' <-- Stable
• May add 'unstable' option in future
Manual Installation
Go to
 http://nodejs.org/#download
and get the URL of the current
         stable release
Enki:~ $ wget -q http://nodejs.org/dist/node-
v0.4.10.tar.gz
Enki:~ $ tar xzf node-v0.4.10.tar.gz
Enki:~ $ cd node-v0.4.10
Enki:~/node-v0.4.10 $
Local or system?
Local
Enki:~/node-v0.4.10 $ mkdir ~/local
Enki:~/node-v0.4.10 $ ./configure --prefix=~/local
Checking for program g++ or c++           : /usr/bin/g++
Checking for program cpp             : /usr/bin/cpp
...
Checking for fdatasync(2) with c++      : no
'configure' finished successfully (3.466s)
Enki:~/node-v0.4.10 $ make
Waf: Entering directory `/Users/sh1mmer/node-v0.4.10/build'
DEST_OS: darwin
DEST_CPU: x86
Parallel Jobs: 1
[ 1/69] cc: deps/libeio/eio.c -> build/default/deps/libeio/eio_1.o
/usr/bin/gcc -rdynamic -D_GNU_SOURCE -DHAVE_CONFIG_H=1
-DEV_MULTIPLICITY=0 -pthread -g -O3 -DHAVE_OPENSSL=1 -
DX_STACKSIZE=65536 -D_LARGEFILE_SOURCE -
D_FILE_OFFSET_BITS=64 -DHAVE_FDATASYNC=0 -
DPLATFORM="darwin" -DNDEBUG -Idefault/deps/libeio -I../deps/
libeio -Idefault -I.. ../deps/libeio/eio.c -c -o default/deps/libeio/
eio_1.o
...
Enki:~/node-v0.4.10 $ make install
Waf: Entering directory `/Users/sh1mmer/node-v0.4.10/build'
DEST_OS: darwin
DEST_CPU: x86
Parallel Jobs: 1
* installing build/default/config.h as /Users/sh1mmer/local/
include/node/config.h
* installing build/default/node as /Users/sh1mmer/local/bin/
node
* installing build/default/src/node_config.h as /Users/sh1mmer/
local/include/node/node_config.h
Waf: Leaving directory `/Users/sh1mmer/node-v0.4.10/build'
'install' finished successfully (0.373s)
Enki:~/node-v0.4.7 $
Enki:~/node-v0.4.10 $ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/
local/bin:/usr/X11/bin:/usr/local/git/bin:/Users/
croucher/Code/narwhal/bin:/opt/local/bin:/usr/local/
git/bin:/Users/sh1mmer/bin
Enki:~/node-v0.4.10 $ node -v
-bash: node: command not found
Enki:~/node-v0.4.10 $ echo PATH=~/local/bin:$PATH
>> ~/.profile
Enki:~/node-v0.4.10 $ node -v
v0.4.10
System
Enki:~/node-v0.4.10   $ ./configure
...
Enki:~/node-v0.4.10   $ make
...
Enki:~/node-v0.4.10   $ sudo make install
...
Enki:~/node-v0.4.10   $ node -v
v0.4.7
Enki:~/node-v0.4.10   $
Exercise


• Get Node head from Github using
  Git

• Install to ~/node
Part 2. Basics
node-repl
Interactive JavaScript terminal
$Enki:~ $ node
$Enki:~ $ node
>3>2>1
false
> true == 1
true
> true === 1
false
> console.log('Hello World');
Hello World
> .help
.clear Break, and also clear the local context.
.exit Exit the prompt
.help Show repl options
> .clear
Clearing context...
> .exit
Enki:~ $
Enki:~ $ node
> var foo = "bar";
> foo;
'bar'
> .clear
Clearing context...
> foo
ReferenceError: foo is not defined
   at [object Context]:1:1
   at Interface.<anonymous> (repl:98:19)
   at Interface.emit (events:27:15)
   at Interface._ttyWrite (readline:295:12)
   at Interface.write (readline:132:30)
   at Stream.<anonymous> (repl:79:9)
   at Stream.emit (events:27:15)
   at IOWatcher.callback (net:489:16)
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello Worldn');
}).listen(8124, "127.0.0.1");
console.log('Server running at http://127.0.0.1:8124/');
var http = require('http');

//include the http library
http.createServer(function (req, res) {

}).listen(8124, "127.0.0.1");

//create an http server
//when ‘stuff’ happens call this anonymous function
//listen on port 8124 of the IP 127.0.0.1
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello Worldn');
})

//when ‘stuff’ happens my function fires
//I get a request object and a response object
//I write to the response object header
//HTTP status 200 and content-type ‘text/plain’
//close the response with the body:
//Hello World
console.log('Server running at http://127.0.0.1:8124/');

//write Server is running at http://127.0.0.1:8124/
//to the console
Interactive Debugging
Enki:~/Code/node-examples $ node --debug
helloworld.js
debugger listening on port 5858
Server running at http://127.0.0.1:8124/
Enki:~ $ npm install node-inspector
node-inspector@0.1.8 ./node_modules/node-inspector
       websocket-server@1.4.04
      paperboy@0.0.2
Enki:~ $ node-inspector
visit http://0.0.0.0:8080/debug?port=5858 to start
debugging
Exercises
• Modify the HTTP server to return the text
  "I'm learning Node"
• Change the HTTP response to HTML and
  return your text in an HTML page
• Return the User-Agent string from the
  browser as part of your HTML page
• Return different textual responses to 2 (or
  more) browsers
Part 3. Getting Stuck In
HTTP Client
var http = require('http');

var request = http.request({'host': 'www.google.com',
'port': 80, 'path': '/', 'method':'GET'});
request.on('response', function (response) {
  console.log('STATUS: ' + response.statusCode);
  console.log('HEADERS: ' +
JSON.stringify(response.headers));
  response.setEncoding('utf8');
  response.on('data', function (chunk) {
    console.log('BODY: ' + chunk);
  });
Streaming API
write(data)
               write(data)       Destination
Request
                  end()         (google.com)


            response(headers)
              data(chunk)
              data(chunk)
 Response     data(chunk)
                  end()
Exercise
• Fetch the NYTimes.com and output
  the contents to the console

• Create a web server
 • Create an HTTP client
 • POST data to your web server
 • Output the POST data to console
Events
object.on('event', function() {
  //stuff
} );
EventEmitter
EventEmitter

• manage "event handlers"
 • list of functions to be called per
    event

• provide mechanism to trigger
  events
var util = require('util'),
  EE = require('events').EventEmitter;

util.inherits(MyClass, EE);

var myObj = new MyClass();

//nb using first class functions
myObj.on('something', function);
exports.inherits = function (ctor, superCtor) {
   ctor.super_ = superCtor;
   ctor.prototype = Object.create(superCtor.prototype, {
       constructor: {
         value: ctor,
         enumerable: false
       }
   });
};
More than just core
CommonJS Modules
Library format for
       SSJS
math.js
exports.add = function() {
   var sum = 0, i = 0, args = arguments, l = args.length;
   while (i < l) {
      sum += args[i++];
   }
   return sum;
};

increment.js
var add = require('math').add;
exports.increment = function(val) {
   return add(val, 1);
};


var inc = require('increment').increment;
var a = 1;
inc(a); // 2
Protip
exports.awesome = function() {
  //yay my code is awesomesauce
};

var exports.fail = function() {
  //exports is a global
  //by redeclaring it as
  //a local variable
  //your code _will_ fail
};
Exercise
• Create a CommonJS module called "fish"
 • Provide functions to:
   • swim
   • mouth breath
   • flop around
• Import your module into a node project
• Call the various functions
Node Package
Manager (NPM)
NPM is written in
JavaScript for Node
Enki:~ $ cat `which npm`
#!/usr/bin/env node
;(function () { // wrapper in case we're in module_context mode
var log = require("../lib/utils/log")
log.waitForConfig()
log.info("ok", "it worked if it ends with")

var fs = require("../lib/utils/graceful-fs")
 , path = require("path")
 , sys = require("../lib/utils/sys")
 , npm = require("../npm")
 , ini = require("../lib/utils/ini")
 , rm = require("../lib/utils/rm-rf")
Enki:~/Code/node(master) $ npm install express
express@2.3.11 ../../node_modules/express
       mime@1.2.2
      connect@1.4.2
      qs@0.1.0
Enki:~/Code/node(master) $
Yay. Easy.
Install instructions
  https://github.com/isaacs/npm
Express.js
Sinatra Style MVC
   framework
var app = require('express').createServer();

app.get('/', function(req, res){
    res.send('hello world');
});

app.listen(3000);
HTTP Verb Oriented
Middleware
app.use(express.bodyParser());
app.use(express.cookieParser());

app.post('/', function(req, res){
  // Perhaps we posted several items with a form
  // (use the bodyParser() middleware for this)
  var items = req.body.items;
  console.log(items);
  res.send('logging');
});
Templating
var express = require("express");

app.configure(function () {
     var public = __dirname + "/../public/";
     public = require("path").normalize(public);

      app.set("views", __dirname + "/views");
      app.set("view engine", "jade");
});

app.get("/", function (req, res) {
     res.render("index", {
        locals : {
           h1: 'Router Stats',
           scripts : [
              "/public/smoothie.js",
              "/public/raphael.js",
              "/public/base.js",
              "/public/gfx.js",
              "/public/explore.js",
              "/public/app.js"
           ]
        }
     }
});
Exercise

• Create an Express server
• Serve two different pages based on
  value of the HTTP Get param "page"

• Create a redirect from /old to /new
• Set a cookie on the client
Express in depth
Routes
• Routes are based on verbs
 • GET
 • POST
 • PUT
 • DELETE
 • ALL (not a real verb, but obvious)
Simple routes
app.get(‘/’, function(req,res) {
  res.send(‘hello root’);
});
Routes with
 variables
app.get(‘/user/:id’, function(req,res) {
  res.send(‘hello ‘ + req.params.id);
});
Optional flags in
     routes
app.get(‘/:filename?’, function(req,res) {
  if(req.params.filename) {
    res.send(req.params.filename);
  } else {
    res.send(‘root’);
  }
});
Regex as routes
app.get(///, function(req, res) {
  //like using ‘/’ ?
  res.send(‘/’);
});
app.get(/^/d+/?$/, function(req,res) {
  res.send(‘matches a number’);
});
app.get(/^/(.+)/?$/, function(req,res) {
  //note translation of %23, etc
  res.send(‘Got: ’ + req.params[0]);
 //also captures become an array
});
Using regex to
define parameters
app.get(‘/index.:format((html|json))’,
function(req,res) {
  res.send('Got: ' + req.params.format);
});
app.get('/:id(d+)', function(req,res) {
  //only digits, right?
  res.send(req.params.id);
});
app.get('/:id(d+)', function(req,res) {
  //escape your  in strings
  res.send(req.params.id);
});
Routing magic:
                Router.js
function normalizePath(path, keys) {
  path = path
   .concat('/?')
   .replace(//(/g, '(?:/')
   .replace(/(/)?(.)?:(w+)(?:((.*?)))?(?)?/g, function(_, slash, format, key,
capture, optional){
     keys.push(key);
     slash = slash || '';
     return ''
      + (optional ? '' : slash)
      + '(?:'
      + (optional ? slash : '')
      + (format || '') + (capture || '([^/]+?)') + ')'
      + (optional || '');
   })
   .replace(/([/.])/g, '$1')
   .replace(/*/g, '(.+)');
  return new RegExp('^' + path + '$', 'i');
}
Routing magic
•   If . before :variable? then . is also optional
•   If ? is not after a variable then only the previous character is
    affected
•   / at the end of URLs automatically optional
•   * Can be used as a wildcard in routes
•   Includes when end of URL is optional
    e.g. '/app/e?'
•   Regex can be used any place in a route string
    e.g. '/app/(d)r?'
Exercises
•   Create an express app with routes that capture '/'
    '/products' and '/services'
•   Create a route that captures the product ID after
    '/product/' e.g. '/product/abc12' and returns it in
    the response
•   Use a regular expression to restrict the ID
    parameter to 3 letter followed by 3-5 numbers
•   Create a route using a regex that matches the
    entire route
Passing Control

• Routes are actually 'stacked middleware'
• You can pass control between routes
• The next() function calls the next matching
  route
app.get('/users/:id', function(req, res, next){
 var id = req.params.id;

  if (checkPermission(id)) {
    // show private page
  } else {
    next();
  }
});

app.get('/users/:id', function(req, res){
  // show public user page
});
Passing Control
•   next() is a function of router (and defined in the
    closure containing the route)
•   router will grab routes in the order they were
    declared
•   e.g. since'/*' will match everything so it should be
    the last route!
•   router doesn't care about verbs so you can use
    all() to operate on all verbs/routes and then use
    next() to pass to get(), put(), etc
Exercises

• Create a simple check for correct product
  IDs if not pass control to a route showing a
  custom error page
• Use app.all() to check user permission
  before showing (mock-up) edit controls on
  a web site
Middleware
It's a pattern
req, res




next()




next()




next()
req, res




next()




next()




                    Dispatcher
next()                 req

                       res
req, res, next
var express = require('express'),
  app = express.createServer();

var middleware = function (req, res, next) {
  req.foo = 'bar';
  next();
};

app.use(middleware);

app.get('/', function(req, res) {
  res.send(req.foo);
var express = require('express'),
  app = express.createServer();

var middleware = function (req, res, next) {
  var send = res.send;
  res.send = function(d) {
    res.send = send;
    res.send('hijacked! ' + d);
  }
  next();
};

app.use(middleware);

app.get('/', function(req, res) {
   res.send('hi');
});
Connect middleware
    (Renamed express.* for convenience)
•   logger            •   profiler

•   bodyParser        •   responseTime

•   cookieParser      •   basicAuth

•   session           •   favicon

•   static            •   vhost

•   errorHandler
var express = require('express'),
  app = express.createServer();

app.use(express.logger());
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(app.router);
app.use(express.static(__dirname + '/images'));
app.use(express.errorHandler());

app.get('/', function(req, res) {
  res.send('<html><img src="/image.png"></html');
});

app.listen(9003);
Ordering matters
Router uses
 "internal
middleware"
var express = require('express'),
  app = express.createServer();

var middleware = function (req, res, next) {
  req.foo = 'bar';
  next();
};

app.get('/', middleware, function(req, res) {
   res.send(req.foo);
});
var a, b, c, d;
a = b = c = d = function(req,res,next) {
  next();
}

var set1 = [a,b];
var set2 = [c,d];
var all = [set1, set2];

app.get('/set1', set1, function(req,res) {
  res.send('output');
});
app.get('/set2', [c,d], function(req,res) {
  res.send('output');
});
app.get('/all', all, function(req,res) {
  res.send('output');
});
Middleware
 factories
Middleware are just
    functions
var a, b, c, d;
a = b = c = d = function(req,res,next) {
  next();
}

var set1 = [a,b];
var set2 = [c,d];
var all = [set1, set2];

app.get('/set1', set1, function(req,res) {
  res.send('output');
});
app.get('/set2', [c,d], function(req,res) {
  res.send('output');
});
app.get('/all', all, function(req,res) {
  res.send('output');
});
var mFactory = function(letter) {
  return function(req,res,next) {
    var send = res.send;
    res.send = function(d) {
      res.send = send;
      res.send(letter + ' ' + d);
    }
    next();
  }
};

var set1 = [mFactory('a'),mFactory('b')];
var set2 = [mFactory('c'),mFactory('d')];
var all = [set1, set2];

app.get('/set1', set1, function(req,res) {
  res.send('output');
});
app.get('/set2', set2, function(req,res) {
  res.send('output');
});
app.get('/all', all, function(req,res) {
  res.send('output');
});
Exercise
• Create a middleware to detect mobile
  phone browsers and attach a boolean to
  req

• Create an express app that serves up links
  to an image using staticProvider

• Modify Profiler to profile your app and
  write each profile to a log file

• Create a middleware factory that sets the
  HTTP Expires header based on roles
Error handling
function NotFound(msg){
  this.name = 'NotFound';
  Error.call(this, msg);
  Error.captureStackTrace(this, arguments.callee);
}

NotFound.prototype.__proto__ = Error.prototype;

app.get('/404', function(req, res){
  throw new NotFound;
});

app.get('/500', function(req, res){
  throw new Error('keyboard cat!');
});
app.error(function(err, req, res, next){
    if (err instanceof NotFound) {
        res.render('404.jade');
    } else {
        next(err);
    }
});
View Rendering
app.get('/', function(req, res){
    res.render('index.ejs', { title: 'Falsy Demo' });
});
Enki:~/Code/express-demo $ tree
.
       app.js
      lib
      public
      views
            index.ejs
            layout.ejs
            layout1.ejs
            partials
              stylesheet.ejs

4 directories, 5 files
Enki:~/Code/express-demo $
Don't forget to
           install

npm install ejs
npm install jade
layout.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
   <meta charset="utf-8">
   <title><%= title %></title>
  </head>
  <body>
    <%- body %>
  </body>
</html>
• layout is a framework unless you
  turn it off

• body is a special variable for layout
  referring to the file specified
app.set('view engine', 'ejs');

app.get('/', function(req,res) {
  res.render('index', { title:'Falsy Demo'});
});
//global

app.set('view options'), {
  layout: false;
});

//or per route

res.render(index, {layout: false});
Partial views
View Partials

• Repeating elements
• Take a collection
• Iterate over the collection
• "Built in" variables for managing
  collections
layout.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
   <meta charset="utf-8">
   <%- partial('stylesheet', stylesheets) %>
   <title><%= title %></title>
  </head>
  <body>
    <h1><%= header %></h1>
    <%- body %>
  </body>
</html>
partials/stylesheet.ejs



<link rel="stylesheet" type="text/css" href="<%-
stylesheet %>">
res.render('index', {
  locals: {'title': title,
        'header': header,
        'content': content,
        stylesheets: ['/public/style.css']
        },
  }
);
Exercises
•   Create an express server that use jade, haml, ejs to
    render an index page

•   Create a public folder and include file from it (CSS,
    images, etc) in your layout

•   Create a route for '/blog/id' to accept only digits

•   Make a 'fake database' (array) of blog posts and use a
    middleware to validate each id is valid

•   Create a view for the '/blog/id' show the correct post

•   Use a view partial to a preview of each blog post on
    the index page
Questions

OSCON 2011 - Node.js Tutorial

  • 1.
    Node.js Workshop Tom Hughes-Croucher ChiefEvangelist / Node Tech Lead @sh1mmer tom@joyent.com
  • 2.
    Overview • Introduction • Why Server-Side JavaScript? • What is Node? • Using Node • Understanding Node • Node Ecosystem • Programming Style • More Complex applications • Deploying Node apps to the cloud
  • 3.
    Introduction • Tom Hughes-Croucher •Chief Evangelist at Joyent • Node.js core contributor • Author of "Up and Running with Node.js"
  • 4.
    Scalable Server-Side Codewith JavaScript Node Up and Running Tom Hughes-Croucher
  • 5.
  • 6.
    Why Server-Side JavaScript?
  • 7.
  • 8.
    Massive Code baseof jQuery and other JS libraries
  • 9.
    Laziness or “I’msick of writing stuff twice” I could have said efficiency, but I think we all secretly long to sit around in our underwear.
  • 10.
    Progressive Enhancement is free* RememberWWCD (What Would Crockford Do) *close enough
  • 11.
    TL;DR: SSJS is Awesome Like a Unicorn riding a Narwhal
  • 13.
    If SSJS isso awesome why is it "new"?
  • 14.
  • 16.
    “Yahoo!'s corporate mottois: Don't be eval().“
  • 17.
  • 18.
    Runtimes • V8 (Google),C++ • Spider Monkey (Mozilla), C++ • Rhino (Mozilla), Java
  • 19.
    V8 Spider Monkey JavaScript Performance
  • 20.
    8x Sep 08! Mar 11!
  • 21.
  • 22.
    Node { !
  • 23.
  • 24.
  • 25.
    Summary • Benefits ofSSJS • Lots of JavaScript expertise • Lots of web code in JS libraries • Write once, run anywhere • Progressive Enhancement • Why SSJS happened now • Professionalism in JavaScript • New generation of JavaScript runtimes
  • 26.
  • 27.
    Node • JavaScript programmingenvironment • Uses V8 runtime • Event Driven • Non-blocking libraries • Supports CommonJS module format • Supports C/C++ based add-ons
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
    concurrency=300, Smaller isBetter 400 300 response time (ms) server nginx 200 thin tornado node_buffer 100 24 26 28 210 212 214 216 218 response size (bytes)
  • 34.
    3. It's easyto extend
  • 35.
  • 36.
    4. Node is_not_ Rails/ Django/etc
  • 37.
    Node is bare-boneto the metal
  • 38.
    However, the Node communityare making Rails/Django/etc
  • 39.
  • 41.
  • 42.
  • 43.
    Using Node • Part1. Installation • Part 2. Basics • Part 3. Getting stuck in
  • 44.
  • 45.
  • 46.
    Enki:~ $ wget-q http://github.com/isaacs/nave/raw/ master/nave.sh Enki:~ $ chmod 755 nave.sh Enki:~ $ ./nave.sh install latest
  • 47.
    Nave • Installs andversions Node • Allows Node shells with specific versions • Allows you to get 'latest' <-- Stable • May add 'unstable' option in future
  • 48.
  • 49.
    Go to http://nodejs.org/#download andget the URL of the current stable release
  • 50.
    Enki:~ $ wget-q http://nodejs.org/dist/node- v0.4.10.tar.gz Enki:~ $ tar xzf node-v0.4.10.tar.gz Enki:~ $ cd node-v0.4.10 Enki:~/node-v0.4.10 $
  • 51.
  • 52.
  • 53.
    Enki:~/node-v0.4.10 $ mkdir~/local Enki:~/node-v0.4.10 $ ./configure --prefix=~/local Checking for program g++ or c++ : /usr/bin/g++ Checking for program cpp : /usr/bin/cpp ... Checking for fdatasync(2) with c++ : no 'configure' finished successfully (3.466s)
  • 54.
    Enki:~/node-v0.4.10 $ make Waf:Entering directory `/Users/sh1mmer/node-v0.4.10/build' DEST_OS: darwin DEST_CPU: x86 Parallel Jobs: 1 [ 1/69] cc: deps/libeio/eio.c -> build/default/deps/libeio/eio_1.o /usr/bin/gcc -rdynamic -D_GNU_SOURCE -DHAVE_CONFIG_H=1 -DEV_MULTIPLICITY=0 -pthread -g -O3 -DHAVE_OPENSSL=1 - DX_STACKSIZE=65536 -D_LARGEFILE_SOURCE - D_FILE_OFFSET_BITS=64 -DHAVE_FDATASYNC=0 - DPLATFORM="darwin" -DNDEBUG -Idefault/deps/libeio -I../deps/ libeio -Idefault -I.. ../deps/libeio/eio.c -c -o default/deps/libeio/ eio_1.o ...
  • 55.
    Enki:~/node-v0.4.10 $ makeinstall Waf: Entering directory `/Users/sh1mmer/node-v0.4.10/build' DEST_OS: darwin DEST_CPU: x86 Parallel Jobs: 1 * installing build/default/config.h as /Users/sh1mmer/local/ include/node/config.h * installing build/default/node as /Users/sh1mmer/local/bin/ node * installing build/default/src/node_config.h as /Users/sh1mmer/ local/include/node/node_config.h Waf: Leaving directory `/Users/sh1mmer/node-v0.4.10/build' 'install' finished successfully (0.373s) Enki:~/node-v0.4.7 $
  • 56.
    Enki:~/node-v0.4.10 $ echo$PATH /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/ local/bin:/usr/X11/bin:/usr/local/git/bin:/Users/ croucher/Code/narwhal/bin:/opt/local/bin:/usr/local/ git/bin:/Users/sh1mmer/bin Enki:~/node-v0.4.10 $ node -v -bash: node: command not found Enki:~/node-v0.4.10 $ echo PATH=~/local/bin:$PATH >> ~/.profile Enki:~/node-v0.4.10 $ node -v v0.4.10
  • 57.
  • 58.
    Enki:~/node-v0.4.10 $ ./configure ... Enki:~/node-v0.4.10 $ make ... Enki:~/node-v0.4.10 $ sudo make install ... Enki:~/node-v0.4.10 $ node -v v0.4.7 Enki:~/node-v0.4.10 $
  • 59.
    Exercise • Get Nodehead from Github using Git • Install to ~/node
  • 60.
  • 61.
  • 62.
  • 63.
    $Enki:~ $ node >3>2>1 false >true == 1 true > true === 1 false
  • 64.
    > console.log('Hello World'); HelloWorld > .help .clear Break, and also clear the local context. .exit Exit the prompt .help Show repl options > .clear Clearing context... > .exit Enki:~ $
  • 65.
    Enki:~ $ node >var foo = "bar"; > foo; 'bar' > .clear Clearing context... > foo ReferenceError: foo is not defined at [object Context]:1:1 at Interface.<anonymous> (repl:98:19) at Interface.emit (events:27:15) at Interface._ttyWrite (readline:295:12) at Interface.write (readline:132:30) at Stream.<anonymous> (repl:79:9) at Stream.emit (events:27:15) at IOWatcher.callback (net:489:16)
  • 66.
    var http =require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello Worldn'); }).listen(8124, "127.0.0.1"); console.log('Server running at http://127.0.0.1:8124/');
  • 67.
    var http =require('http'); //include the http library
  • 68.
    http.createServer(function (req, res){ }).listen(8124, "127.0.0.1"); //create an http server //when ‘stuff’ happens call this anonymous function //listen on port 8124 of the IP 127.0.0.1
  • 69.
    http.createServer(function (req, res){ res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello Worldn'); }) //when ‘stuff’ happens my function fires //I get a request object and a response object //I write to the response object header //HTTP status 200 and content-type ‘text/plain’ //close the response with the body: //Hello World
  • 70.
    console.log('Server running athttp://127.0.0.1:8124/'); //write Server is running at http://127.0.0.1:8124/ //to the console
  • 71.
  • 72.
    Enki:~/Code/node-examples $ node--debug helloworld.js debugger listening on port 5858 Server running at http://127.0.0.1:8124/
  • 79.
    Enki:~ $ npminstall node-inspector node-inspector@0.1.8 ./node_modules/node-inspector websocket-server@1.4.04 paperboy@0.0.2 Enki:~ $ node-inspector visit http://0.0.0.0:8080/debug?port=5858 to start debugging
  • 81.
    Exercises • Modify theHTTP server to return the text "I'm learning Node" • Change the HTTP response to HTML and return your text in an HTML page • Return the User-Agent string from the browser as part of your HTML page • Return different textual responses to 2 (or more) browsers
  • 82.
  • 83.
  • 84.
    var http =require('http'); var request = http.request({'host': 'www.google.com', 'port': 80, 'path': '/', 'method':'GET'}); request.on('response', function (response) { console.log('STATUS: ' + response.statusCode); console.log('HEADERS: ' + JSON.stringify(response.headers)); response.setEncoding('utf8'); response.on('data', function (chunk) { console.log('BODY: ' + chunk); });
  • 85.
  • 86.
    write(data) write(data) Destination Request end() (google.com) response(headers) data(chunk) data(chunk) Response data(chunk) end()
  • 87.
    Exercise • Fetch theNYTimes.com and output the contents to the console • Create a web server • Create an HTTP client • POST data to your web server • Output the POST data to console
  • 88.
  • 89.
  • 90.
  • 91.
    EventEmitter • manage "eventhandlers" • list of functions to be called per event • provide mechanism to trigger events
  • 92.
    var util =require('util'), EE = require('events').EventEmitter; util.inherits(MyClass, EE); var myObj = new MyClass(); //nb using first class functions myObj.on('something', function);
  • 93.
    exports.inherits = function(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false } }); };
  • 94.
  • 95.
  • 96.
  • 97.
    math.js exports.add = function(){ var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) { sum += args[i++]; } return sum; }; increment.js var add = require('math').add; exports.increment = function(val) { return add(val, 1); }; var inc = require('increment').increment; var a = 1; inc(a); // 2
  • 98.
    Protip exports.awesome = function(){ //yay my code is awesomesauce }; var exports.fail = function() { //exports is a global //by redeclaring it as //a local variable //your code _will_ fail };
  • 99.
    Exercise • Create aCommonJS module called "fish" • Provide functions to: • swim • mouth breath • flop around • Import your module into a node project • Call the various functions
  • 100.
  • 101.
    NPM is writtenin JavaScript for Node
  • 102.
    Enki:~ $ cat`which npm` #!/usr/bin/env node ;(function () { // wrapper in case we're in module_context mode var log = require("../lib/utils/log") log.waitForConfig() log.info("ok", "it worked if it ends with") var fs = require("../lib/utils/graceful-fs") , path = require("path") , sys = require("../lib/utils/sys") , npm = require("../npm") , ini = require("../lib/utils/ini") , rm = require("../lib/utils/rm-rf")
  • 103.
    Enki:~/Code/node(master) $ npminstall express express@2.3.11 ../../node_modules/express mime@1.2.2 connect@1.4.2 qs@0.1.0 Enki:~/Code/node(master) $
  • 104.
  • 105.
    Install instructions https://github.com/isaacs/npm
  • 106.
  • 107.
  • 108.
    var app =require('express').createServer(); app.get('/', function(req, res){ res.send('hello world'); }); app.listen(3000);
  • 109.
  • 110.
  • 111.
    app.use(express.bodyParser()); app.use(express.cookieParser()); app.post('/', function(req, res){ // Perhaps we posted several items with a form // (use the bodyParser() middleware for this) var items = req.body.items; console.log(items); res.send('logging'); });
  • 112.
  • 113.
    var express =require("express"); app.configure(function () { var public = __dirname + "/../public/"; public = require("path").normalize(public); app.set("views", __dirname + "/views"); app.set("view engine", "jade"); }); app.get("/", function (req, res) { res.render("index", { locals : { h1: 'Router Stats', scripts : [ "/public/smoothie.js", "/public/raphael.js", "/public/base.js", "/public/gfx.js", "/public/explore.js", "/public/app.js" ] } } });
  • 114.
    Exercise • Create anExpress server • Serve two different pages based on value of the HTTP Get param "page" • Create a redirect from /old to /new • Set a cookie on the client
  • 115.
  • 116.
    Routes • Routes arebased on verbs • GET • POST • PUT • DELETE • ALL (not a real verb, but obvious)
  • 117.
  • 118.
    app.get(‘/’, function(req,res) { res.send(‘hello root’); });
  • 119.
  • 120.
    app.get(‘/user/:id’, function(req,res) { res.send(‘hello ‘ + req.params.id); });
  • 121.
  • 122.
    app.get(‘/:filename?’, function(req,res) { if(req.params.filename) { res.send(req.params.filename); } else { res.send(‘root’); } });
  • 123.
  • 124.
    app.get(///, function(req, res){ //like using ‘/’ ? res.send(‘/’); });
  • 125.
    app.get(/^/d+/?$/, function(req,res) { res.send(‘matches a number’); });
  • 126.
    app.get(/^/(.+)/?$/, function(req,res) { //note translation of %23, etc res.send(‘Got: ’ + req.params[0]); //also captures become an array });
  • 127.
  • 128.
    app.get(‘/index.:format((html|json))’, function(req,res) { res.send('Got: ' + req.params.format); });
  • 129.
    app.get('/:id(d+)', function(req,res) { //only digits, right? res.send(req.params.id); });
  • 130.
    app.get('/:id(d+)', function(req,res) { //escape your in strings res.send(req.params.id); });
  • 131.
    Routing magic: Router.js function normalizePath(path, keys) { path = path .concat('/?') .replace(//(/g, '(?:/') .replace(/(/)?(.)?:(w+)(?:((.*?)))?(?)?/g, function(_, slash, format, key, capture, optional){ keys.push(key); slash = slash || ''; return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || '([^/]+?)') + ')' + (optional || ''); }) .replace(/([/.])/g, '$1') .replace(/*/g, '(.+)'); return new RegExp('^' + path + '$', 'i'); }
  • 132.
    Routing magic • If . before :variable? then . is also optional • If ? is not after a variable then only the previous character is affected • / at the end of URLs automatically optional • * Can be used as a wildcard in routes • Includes when end of URL is optional e.g. '/app/e?' • Regex can be used any place in a route string e.g. '/app/(d)r?'
  • 133.
    Exercises • Create an express app with routes that capture '/' '/products' and '/services' • Create a route that captures the product ID after '/product/' e.g. '/product/abc12' and returns it in the response • Use a regular expression to restrict the ID parameter to 3 letter followed by 3-5 numbers • Create a route using a regex that matches the entire route
  • 134.
    Passing Control • Routesare actually 'stacked middleware' • You can pass control between routes • The next() function calls the next matching route
  • 135.
    app.get('/users/:id', function(req, res,next){ var id = req.params.id; if (checkPermission(id)) { // show private page } else { next(); } }); app.get('/users/:id', function(req, res){ // show public user page });
  • 136.
    Passing Control • next() is a function of router (and defined in the closure containing the route) • router will grab routes in the order they were declared • e.g. since'/*' will match everything so it should be the last route! • router doesn't care about verbs so you can use all() to operate on all verbs/routes and then use next() to pass to get(), put(), etc
  • 137.
    Exercises • Create asimple check for correct product IDs if not pass control to a route showing a custom error page • Use app.all() to check user permission before showing (mock-up) edit controls on a web site
  • 138.
  • 139.
  • 140.
  • 141.
    req, res next() next() Dispatcher next() req res
  • 142.
  • 143.
    var express =require('express'), app = express.createServer(); var middleware = function (req, res, next) { req.foo = 'bar'; next(); }; app.use(middleware); app.get('/', function(req, res) { res.send(req.foo);
  • 144.
    var express =require('express'), app = express.createServer(); var middleware = function (req, res, next) { var send = res.send; res.send = function(d) { res.send = send; res.send('hijacked! ' + d); } next(); }; app.use(middleware); app.get('/', function(req, res) { res.send('hi'); });
  • 145.
    Connect middleware (Renamed express.* for convenience) • logger • profiler • bodyParser • responseTime • cookieParser • basicAuth • session • favicon • static • vhost • errorHandler
  • 146.
    var express =require('express'), app = express.createServer(); app.use(express.logger()); app.use(express.bodyParser()); app.use(express.cookieParser()); app.use(app.router); app.use(express.static(__dirname + '/images')); app.use(express.errorHandler()); app.get('/', function(req, res) { res.send('<html><img src="/image.png"></html'); }); app.listen(9003);
  • 147.
  • 148.
  • 149.
    var express =require('express'), app = express.createServer(); var middleware = function (req, res, next) { req.foo = 'bar'; next(); }; app.get('/', middleware, function(req, res) { res.send(req.foo); });
  • 150.
    var a, b,c, d; a = b = c = d = function(req,res,next) { next(); } var set1 = [a,b]; var set2 = [c,d]; var all = [set1, set2]; app.get('/set1', set1, function(req,res) { res.send('output'); }); app.get('/set2', [c,d], function(req,res) { res.send('output'); }); app.get('/all', all, function(req,res) { res.send('output'); });
  • 151.
  • 152.
  • 153.
    var a, b,c, d; a = b = c = d = function(req,res,next) { next(); } var set1 = [a,b]; var set2 = [c,d]; var all = [set1, set2]; app.get('/set1', set1, function(req,res) { res.send('output'); }); app.get('/set2', [c,d], function(req,res) { res.send('output'); }); app.get('/all', all, function(req,res) { res.send('output'); });
  • 154.
    var mFactory =function(letter) { return function(req,res,next) { var send = res.send; res.send = function(d) { res.send = send; res.send(letter + ' ' + d); } next(); } }; var set1 = [mFactory('a'),mFactory('b')]; var set2 = [mFactory('c'),mFactory('d')]; var all = [set1, set2]; app.get('/set1', set1, function(req,res) { res.send('output'); }); app.get('/set2', set2, function(req,res) { res.send('output'); }); app.get('/all', all, function(req,res) { res.send('output'); });
  • 155.
    Exercise • Create amiddleware to detect mobile phone browsers and attach a boolean to req • Create an express app that serves up links to an image using staticProvider • Modify Profiler to profile your app and write each profile to a log file • Create a middleware factory that sets the HTTP Expires header based on roles
  • 156.
  • 157.
    function NotFound(msg){ this.name = 'NotFound'; Error.call(this, msg); Error.captureStackTrace(this, arguments.callee); } NotFound.prototype.__proto__ = Error.prototype; app.get('/404', function(req, res){ throw new NotFound; }); app.get('/500', function(req, res){ throw new Error('keyboard cat!'); });
  • 158.
    app.error(function(err, req, res,next){ if (err instanceof NotFound) { res.render('404.jade'); } else { next(err); } });
  • 159.
  • 160.
    app.get('/', function(req, res){ res.render('index.ejs', { title: 'Falsy Demo' }); });
  • 161.
    Enki:~/Code/express-demo $ tree . app.js lib public views index.ejs layout.ejs layout1.ejs partials stylesheet.ejs 4 directories, 5 files Enki:~/Code/express-demo $
  • 162.
    Don't forget to install npm install ejs npm install jade
  • 163.
    layout.ejs <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title><%= title %></title> </head> <body> <%- body %> </body> </html>
  • 164.
    • layout isa framework unless you turn it off • body is a special variable for layout referring to the file specified
  • 165.
    app.set('view engine', 'ejs'); app.get('/',function(req,res) { res.render('index', { title:'Falsy Demo'}); });
  • 166.
    //global app.set('view options'), { layout: false; }); //or per route res.render(index, {layout: false});
  • 167.
  • 168.
    View Partials • Repeatingelements • Take a collection • Iterate over the collection • "Built in" variables for managing collections
  • 169.
    layout.ejs <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <%- partial('stylesheet', stylesheets) %> <title><%= title %></title> </head> <body> <h1><%= header %></h1> <%- body %> </body> </html>
  • 170.
  • 171.
    res.render('index', { locals: {'title': title, 'header': header, 'content': content, stylesheets: ['/public/style.css'] }, } );
  • 172.
    Exercises • Create an express server that use jade, haml, ejs to render an index page • Create a public folder and include file from it (CSS, images, etc) in your layout • Create a route for '/blog/id' to accept only digits • Make a 'fake database' (array) of blog posts and use a middleware to validate each id is valid • Create a view for the '/blog/id' show the correct post • Use a view partial to a preview of each blog post on the index page
  • 173.