INT RO TO NO D E.
JS
L E VE L O N E -
WHAT IS NODE.JS?
Allows you to build scalable network applications using JavaScript on the server-side. Node.js V8 JavaScript Runtime
e d co C ly st o m s it se u ca e Its fast b
INTRO TO NODE.JS
WHAT COULD YOU BUILD?
Websocket Server Like a Fast File Upload Client Ad Server Any Real-Time Data Apps
chat server
INTRO TO NODE.JS
WHAT IS NODE.JS NOT?
A Web Framework For Beginners Its very low level Multi-threaded r e v r se d e ad e r h t e gl n si a You can think of it as
INTRO TO NODE.JS
OBJECTIVE: PRINT FILE CONTENTS
Blocking Code
Read file from Filesystem, set equal to contents Print contents Do something else
Non-Blocking Code
Read file from Filesystem whenever youre complete, print the contents Do Something else
This is a Callback
INTRO TO NODE.JS
BLOCKING VS NON-BLOCKING
Blocking Code
var contents = fs.readFileSync('/etc/hosts'); console.log(contents); console.log('Doing something else');
e t e l p m o c l i t n u s s e c o r p Stop
Non-Blocking Code
fs.readFile('/etc/hosts', function(err, contents) { console.log(contents); }); console.log('Doing something else');
INTRO TO NODE.JS
CALLBACK ALTERNATE SYNTAX
fs.readFile('/etc/hosts', function(err, contents) { console.log(contents); });
Same as
var callback = function(err, contents) { console.log(contents); } fs.readFile('/etc/hosts', callback);
INTRO TO NODE.JS
BLOCKING VS NON-BLOCKING
var callback = function(err, contents) { console.log(contents); } fs.readFile('/etc/hosts', callback); fs.readFile('/etc/inetcfg', callback);
blocking 0s non-blocking 0s
INTRO TO NODE.JS
5s 5s
10s 10s
NODE.JS HELLO DOG
hello.js
var http = require('http');
How we require modules
http.createServer(function(request, response) { response.writeHead(200);
console.log('Listening on port 8080...');
Status code in header response.write("Hello, this is dog."); Response body response.end(); Close the connection }).listen(8080); Listen for connections on this port
$ node hello.js
Run the server
$ curl http://localhost:8080 Hello, this is dog.
Listening on port 8080...
THE EVENT LOOP
var http = require('http'); http.createServer(function(request, response) { ... }).listen(8080); console.log('Listening on port 8080...');
Starts the Event Loop when finished Run the Callback Known Events Checking for Events
request
WHY JAVASCRIPT?
JavaScript has certain characteristics that make it very different than other dynamic languages, namely that it has no concept of threads. Its model of concurrency is completely based around events. - Ryan Dahl
INTRO TO NODE.JS
THE EVENT LOOP
Event Queue
close request
Known Events Checking for Events
request connection close
Events processed one at a time
WITH LONG RUNNING PROCESS
var http = require('http'); http.createServer(function(request, response) { response.writeHead(200); response.write("Dog is running."); setTimeout(function(){ response.end(); }, 5000); }).listen(8080); response.write("Dog is done.");
Represent long running process
5000ms = 5 seconds
INTRO TO NODE.JS
TWO CALLBACKS HERE
var http = require('http'); http.createServer(function(request, response) { response.writeHead(200); response.write("Dog is running."); setTimeout(function(){ response.write("Dog is done."); response.end(); }, 5000); }).listen(8080);
request timeout
INTRO TO NODE.JS
TWO CALLBACKS TIMELINE
Request comes in, triggers request event Request Callback executes setTimeout registered Request comes in, triggers request event Request Callback executes setTimeout registered triggers setTimeout event request setTimeout Callback executes triggers setTimeout event timeout setTimeout Callback
0s
5s
10s
WITH BLOCKING TIMELINE
Request comes in, triggers request event Request Callback executes setTimeout executed Request comes in, waits for server triggers setTimeout event setTimeout Callback executed Request comes in Request Callback executes
Wasted Time
0s
5s
10s
TYPICAL BLOCKING THINGS
Calls out to web services Reads/Writes on the Database Calls to extensions
INTRO TO NODE.JS
E VE NTS
L E VE L T WO -
EVENTS IN THE DOM
The DOM
The DOM triggers Events you can listen for those events
click submit
attach
When click event is triggered
EVENTS
events
hover
$("p").on("click", function(){ ... });
EVENTS IN NODE
net.Server EventEmitter fs.readStream EventEmitter
Many objects in Node emit events
request
event
data
event
EVENTS
CUSTOM EVENT EMITTERS
var EventEmitter = require('events').EventEmitter; var logger = new EventEmitter(); logger.on('error', function(message){ console.log('ERR: ' + message); }); logger.emit('error', 'Spilled Milk'); ERR: Spilled Milk logger.emit('error', 'Eggs Cracked'); ERR: Eggs Cracked
error
events
warn
info
listen for error event
EVENTS
EVENTS IN NODE
net.Server EventEmitter
Many objects in Node emit events emit
request
attach
function(request, response){ .. }
event
When request event is emitted
EVENTS
HTTP ECHO SERVER
http.createServer(function(request, response){ ... });
But what is really going on here? http://nodejs.org/api/
EVENTS
BREAKING IT DOWN
http.createServer(function(request, response){ ... });
EVENTS
ALTERNATE SYNTAX
http.createServer(function(request, response){ ... });
Same as
var server = http.createServer(); server.on('request', function(request, response){ ... });
This is how we add add event listeners
server.on('close', function(){ ... });
EVENTS
STREA MS
L EVE L TH R E E -
WHAT ARE STREAMS?
Start Processing Immediately
Streams can be readable, writeable, or both
STREAMS
STREAMING RESPONSE
readable stream
response.writeHead(200); response.write("Dog is running."); setTimeout(function(){ response.write("Dog is done."); response.end(); }, 5000); }).listen(8080);
writable stream
http.createServer(function(request, response) {
Our clients receive
STREAMS
"Dog is running." (5 seconds later) "Dog is done."
HOW TO READ FROM THE REQUEST?
Readable Stream EventEmitter
emit
events
data end
Lets print what we receive from the request.
http.createServer(function(request, response) { response.writeHead(200); request.on('data', function(chunk) { console.log(chunk.toString()); }); request.on('end', function() { response.end(); }); }).listen(8080)
STREAMS
LETS CREATE AN ECHO SERVER
http.createServer(function(request, response) { response.writeHead(200); request.on('data', function(chunk) { response.write(chunk); request.pipe(response); }); request.on('end', function() { response.end(); }); }).listen(8080)
STREAMS
LETS CREATE AN ECHO SERVER!
http.createServer(function(request, response) { response.writeHead(200); request.pipe(response); }).listen(8080)
on client Kinda like on the command line
Hello
$ curl -d 'hello' http://localhost:8080
cat 'bleh.txt' | grep 'something'
STREAMS
READING AND WRITING A FILE
var fs = require('fs');
require filesystem module
var file = fs.createReadStream("readme.md"); var newFile = fs.createWriteStream("readme_copy.md"); file.pipe(newFile);
STREAMS
UPLOAD A FILE
var fs = require('fs'); var http = require('http'); http.createServer(function(request, response) { var newFile = fs.createWriteStream("readme_copy.md"); request.pipe(newFile); request.on('end', function() { response.end('uploaded!'); }); }).listen(8080);
$ curl --upload-file readme.md http://localhost:8080 uploaded!
STREAMS
THE AWESOME STREAMING
server
client
original file
storage transferred file non-blocking 5s 10s
0s
BACK PRESSURE!
server Writable stream slower than readable stream original file transferred file
client
storage
Using pipe solves this problem
THINK OF A MILK JUG
milkStream.pause(); Once milk jug is drained milkStream.resume(); });
PIPE SOLVES BACKPRESSURE
Pause when writeStream is full
readStream.on('data', function(chunk) { var buffer_good = writeStream.write(chunk); if (!buffer_good) readStream.pause(); });
Resume when ready to write again
writeStream.on('drain', function(){ readStream.resume(); });
returns false if kernel buffer full
All encapsulated in
readStream.pipe(writeStream);
FILE UPLOADING PROGRESS
STREAMS
FILE UPLOADING PROGRESS
$ curl --upload-file file.jpg http://localhost:8080
Outputs:
progress: progress: progress: progress: progress: ... progress: progress: 3% 6% 9% 12% 13% 99% 100%
Were going to need:
HTTP Server File System
STREAMS
DOCUMENTATION http://nodejs.org/api/
Stability Scores
STREAMS
REMEMBER THIS CODE?
var fs = require('fs'); var http = require('http'); http.createServer(function(request, response) { var newFile = fs.createWriteStream("readme_copy.md"); request.pipe(newFile); request.on('end', function() { response.end('uploaded!'); }); }).listen(8080);
STREAMS
REMEMBER THIS CODE?
http.createServer(function(request, response) { var newFile = fs.createWriteStream("readme_copy.md"); var fileBytes = request.headers['content-length']; var uploadedBytes = 0; request.pipe(newFile); request.on('data', function(chunk) { uploadedBytes += chunk.length; var progress = (uploadedBytes / fileBytes) * 100; response.write("progress: " + parseInt(progress, 10) + "%\n"); }); ... }).listen(8080);
STREAMS
SHOWING PROGRESS
STREAMS
MOD ULES
L EV E L FO U R -
REQUIRING MODULES
var http = require('http'); var fs = require('fs');
http.js fs.js
How does require return the libraries? How does it find these files?
MODULES
LETS CREATE OUR OWN MODULE
custom_hello.js
var hello = function() { console.log("hello!"); } exports = hello;
custom_goodbye.js
exports.goodbye = function() { console.log("bye!"); }
app.js
hello();
exports defines what require returns
var hello = require('./custom_hello'); var gb = require('./custom_goodbye'); gb.goodbye(); require('./custom_goodbye').goodbye();
If we only need to call once
MODULES
EXPORT MULTIPLE FUNCTIONS
my_module.js
var foo = function() { ... } var bar = function() { ... } var baz = function() { ... } exports.foo = foo exports.bar = bar
my_module.js foo bar baz
app.js
var myMod = require('./my_module'); myMod.foo(); myMod.bar();
private
MODULES
MAKING HTTP REQUESTS
var http = require('http');
app.js
var message = "Here's looking at you, kid."; var options = { host: 'localhost', port: 8080, path: '/', method: 'POST' } var request = http.request(options, function(response){ response.on('data', function(data){ console.log(data); }); });
logs response body
request.write(message); request.end();
begins request finishes request
MODULES
ENCAPSULATING THE FUNCTION
var http = require('http');
app.js
var makeRequest = function(message) { var options = { host: 'localhost', port: 8080, path: '/', method: 'POST' } var request = http.request(options, function(response){ response.on('data', function(data){ console.log(data); }); });
Text
request.write(message); request.end(); } makeRequest("Here's looking at you, kid.");
MODULES
CREATING & USING A MODULE
var http = require('http'); var makeRequest = function(message) { ... } exports = makeRequest;
make_request.js
var makeRequest = require('./make_request'); makeRequest("Here's looking at you, kid"); makeRequest("Hello, this is dog");
app.js
Where does require look for modules?
MODULES
REQUIRE SEARCH
var make_request = require('./make_request') var make_request = require('../make_request') var make_request = require('/Users/eric/nodes/make_request')
look in same directory look in parent directory
/Home/eric/my_app/app.js
Search in node_modules directories
var make_request = require('make_request')
/Home/eric/my_app/node_modules/ /Home/eric/node_modules/make_request.js /Home/node_modules/make_request.js /node_modules/make_request.js
MODULES
NPM: THE USERLAND SEA
Package manager for node Comes with node Module Repository Dependency Management Easily publish modules Local Only
Core is small. Userland is large.
MODULES
INSTALLING A NPM MODULE
In /Home/my_app
$ npm install request
https://github.com/mikeal/request
Installs into local node_modules directory
Home my_app node_modules request
In /Home/my_app/app.js
var request = require('request');
Loads from local node_modules directory
MODULES
LOCAL VS GLOBAL
Install modules with executables globally
$ npm install coffee-script -g $ coffee app.coffee
global
Global npm modules cant be required
var coffee = require('coffee-script'); $ npm install coffee-script
Install them locally
var coffee = require('coffee-script');
MODULES
FINDING MODULES
npm registry npm command line
$ npm search request
toolbox.no.de github search
MODULES
DEFINING YOUR DEPENDENCIES
my_app/package.json
{ "name": "My App", "version": "1", "dependencies": { "connect": "1.8.7" } } $ npm install
version number
Installs into the node_modules directory
my_app node_modules connect
MODULES
DEPENDENCIES
my_app/package.json
"dependencies": { "connect": "1.8.7" }
No conflicting modules!
Installs sub-dependencies
my_app node_modules connect
connect connect connect
node_modules node_modules node_modules
qs mime formidable
MODULES
SEMANTIC VERSIONING
"connect": "1.8.7" 1
Major Minor Patch
.
8
Ranges
"connect": "~1" "connect": "~1.8" "connect": "~1.8.7" >=1.0.0 <2.0.0 >=1.8 <2.0.0 >=1.8.7 <1.9.0
Dangerous API could change Considered safe
http://semver.org/
MODULES
EX PRES S
L EV E L FI VE -
EXPRESS
Sinatra inspired web development framework for Node.js -insanely fast, flexible, and simple
Easy route URLs to callbacks Middleware (from Connect) Environment based configuration Redirection helpers File Uploads
EXPRESS
INTRODUCING EXPRESS
var express = require('express'); var app = express.createServer(); $ npm install express
root route
app.get('/', function(request, response) { response.sendfile(__dirname + "/index.html"); }); app.listen(8080); $ curl http://localhost:8080/ > 200 OK
current directory
EXPRESS
EXPRESS ROUTES
var request = require('request'); var url = require('url'); app.get('/tweets/:username', function(req, response) { var username = req.params.username; options = { protocol: "http:", host: 'api.twitter.com', pathname: '/1/statuses/user_timeline.json', query: { screen_name: username, count: 10} } var twitterUrl = url.format(options); request(twitterUrl).pipe(response); });
route definition
app.js
get the last 10 tweets for screen_name
pipe the request to response
EXPRESS
EXPRESS ROUTES
EXPRESS
EXPRESS + HTML
EXPRESS
EXPRESS TEMPLATES
app.get('/tweets/:username', function(req, response) { ... request(url, function(err, res, body) { var tweets = JSON.parse(body); response.render('tweets.ejs', {tweets: tweets, name: username}); }); }); <h1>Tweets for @<%= name %></h1> <ul> <% tweets.forEach(function(tweet){ %> <li><%= tweet.text %></li> <% }); %> </ul>
app.js
tweets.ejs
EXPRESS
EXPRESS TEMPLATES
EXPRESS
TEMPLATE LAYOUTS
<h1>Tweets for @<%= name %></h1> <ul> <% tweets.forEach(function(tweet){ %> <li><%= tweet.text %></li> <% }); %> </ul>
tweets.ejs
<!DOCTYPE html> <html> <head> <title>Tweets</title> </head> <body> <%- body %> </body> </html>
layout.ejs
EXPRESS
EXPRESS TEMPLATES
EXPRESS
S OCKET. IO
L E VE L S IX -
CHATTR
SOCKET.IO
WEBSOCKETS
browser traditional server
Traditional request/response cycle
SOCKET.IO
WEBSOCKETS
browser socket.io
Using duplexed websocket connection
SOCKET.IO
SOCKET.IO FOR WEBSOCKETS
Abstracts websockets with fallbacks
$ npm install socket.io var socket = require('socket.io'); var app = express.createServer(); var io = socket.listen(app);
app.js
io.sockets.on('connection', function(client) { console.log('Client connected...'); });
<script src="/socket.io/socket.io.js"></script> <script>
index.html
var server = io.connect('http://localhost:8080');
</script>
SOCKET.IO
SENDING MESSAGES TO CLIENT
io.sockets.on('connection', function(client) { console.log('Client connected...');
app.js
client.emit('messages', { hello: 'world' }); });
emit the messages event on the client
<script src="/socket.io/socket.io.js"></script> <script>
index.html
var server = io.connect('http://localhost:8080'); server.on('messages', function (data) { alert(data.hello); });
listen for messages events
</script>
SOCKET.IO
CHATTR HELLO WORLD
SOCKET.IO
SENDING MESSAGES TO SERVER
io.sockets.on('connection', function(client) { client.on('messages', function (data) { console.log(data); }); });
<script>
app.js
listen for messages events
index.html
var server = io.connect('http://localhost:8080'); $('#chat_form').submit(function(e){ var message = $('#chat_input').val(); socket.emit('messages', message);
emit the messages event on the server
});
</script>
SOCKET.IO
CHATTR HELLO WORLD
SOCKET.IO
BROADCASTING MESSAGES
app.js
clients
socket.broadcast.emit("message", 'Hello');
server
SOCKET.IO
BROADCASTING MESSAGES
io.sockets.on('connection', function(client) { client.on('messages', function (data) { client.broadcast.emit("messages", data); }); });
<script>
app.js
broadcast message to all other clients connected
index.html
...
</script>
server.on('messages', function(data) { insertMessage(data) });
insert message into the chat
SOCKET.IO
BROADCASTING MESSAGES
SOCKET.IO
SAVING DATA ON THE SOCKET
io.sockets.on('connection', function(client) { client.on('join', function(name) { client.set('nickname', name); }); });
<script>
app.js
set the nickname associated with this client
index.html
var server = io.connect('http://localhost:8080'); server.on('connect', function(data) { $('#status').html('Connected to chattr'); nickname = prompt("What is your nickname?"); server.emit('join', nickname); });
</script>
notify the server of the users nickname
SOCKET.IO
SAVING DATA ON THE CLIENT
io.sockets.on('connection', function(client) { client.on('join', function(name) { client.set('nickname', name); }); client.on('messages', function(data){
app.js
set the nickname associated with this client
client.get('nickname', function(err, name) { client.broadcast.emit("chat", name + ": " + message); }); }); });
get the nickname of this client before broadcasting message
broadcast with the name and message
SOCKET.IO
SAVING DATA ON THE CLIENT
SOCKET.IO
PE RS ISTING DATA
L EV E L SE VE N -
RECENT MESSAGES
PERSISTING DATA
RECENT MESSAGES
io.sockets.on('connection', function(client) { client.on('join', function(name) { client.set('nickname', name); client.broadcast.emit("chat", name + " joined the chat"); }); client.on("messages", function(message){ client.get("nickname", function(error, name) { client.broadcast.emit("messages", name + ": " + message); }); }); });
app.js
PERSISTING DATA
STORING MESSAGES
var messages = [];
store messages in array
app.js
var storeMessage = function(name, data){ messages.push({name: name, data: data}); if (messages.length > 10) { messages.shift(); } }
add message to end of array if more than 10 messages long, remove the last one
io.sockets.on('connection', function(client) { client.on("messages", function(message){ client.get("nickname", function(error, name) { storeMessage(name, message); }); }); });
when client sends a message call storeMessage
PERSISTING DATA
EMITTING MESSAGES
io.sockets.on('connection', function(client) { ... client.on('join', function(name) { messages.forEach(function(message) { client.emit("messages", message.name + ": " + message.data); }); }); });
app.js
iterate through messages array and emit a message on the connecting client for each one
PERSISTING DATA
RECENT MESSAGES
PERSISTING DATA
PERSISTING STORES
MongoDB CouchDB PostgreSQL Memcached Riak
All non-blocking!
Redis is a key-value store
PERSISTING DATA
REDIS DATA STRUCTURES
data structure
Strings Hashes Lists Sets Sorted Sets
commands
SET, GET, APPEND, DECR, INCR... HSET, HGET, HDEL, HGETALL...
LPUSH, LREM, LTRIM, RPOP, LINSERT...
SADD, SREM, SMOVE, SMEMBERS... ZADD, ZREM, ZSCORE, ZRANK...
PERSISTING DATA
REDIS COMMAND DOCUMENTATION
PERSISTING DATA
NODE REDIS
PERSISTING DATA
REDIS
$ npm install redis var redis = require('redis'); var client = redis.createClient(); client.set("message1", "hello, yes this is dog"); client.set("message2", "hello, no this is spider");
key
value
client.get("message1", function(err, reply){ console.log(reply); "hello, yes this is dog" });
commands are non-blocking
PERSISTING DATA
REDIS LISTS: PUSHING
Add a string to the messages list
var message = "Hello, this is dog"; client.lpush("messages", message, function(err, reply){ console.log(reply); "1 });
replies with list length
Add another string to messages
var message = "Hello, no this is spider"; client.lpush("messages", message, function(err, reply){ console.log(reply); "2 });
PERSISTING DATA
REDIS LISTS: RETRIEVING
Using LPUSH & LTRIM
var message = "Oh sorry, wrong number"; client.lpush("messages", message, function(err, reply){ client.ltrim("messages", 0, 1); });
trim keeps first two strings and removes the rest
Retrieving from list
client.lrange("messages", 0, -1, function(err, messages){ console.log(messages); })
replies with all strings in list
["Hello, no this is spider", "Oh sorry, wrong number"]
PERSISTING DATA
CONVERTING MESSAGES TO REDIS
var storeMessage = function(name, data){ messages.push({name: name, data: data}); if (messages.length > 10) { messages.shift(); } }
app.js
Lets use the List data-structure
PERSISTING DATA
CONVERTING STOREMESSAGE
var redisClient = redis.createClient(); var storeMessage = function(name, data){ var message = JSON.stringify({name: name, data: data});
app.js
need to turn object into string to store in redis
redisClient.lpush("messages", message, function(err, response) { redisClient.ltrim("messages", 0, 10); }); }
keeps newest 10 items
PERSISTING DATA
OUTPUT FROM LIST
client.on('join', function(name) { messages.forEach(function(message) { client.emit("messages", message.name + ": " + message.data); }); });
app.js
PERSISTING DATA
OUTPUT FROM LIST
client.on('join', function(name) { messages = messages.reverse();
app.js
redisClient.lrange("messages", 0, -1, function(err, messages){
reverse so they are emitted in correct order parse into JSON object
messages.forEach(function(message) { message = JSON.parse(message);
client.emit("messages", message.name + ": " + message.data); }); }); });
PERSISTING DATA
IN ACTION
PERSISTING DATA
CURRENT CHATTER LIST
Sets are lists of unique data
add & remove members of the names set
client.sadd("names", "Dog"); client.sadd("names", "Spider"); client.sadd("names", "Gregg"); client.srem("names", "Spider");
reply with all members of set
client.smembers("names", function(err, names){ console.log(names); }); ["Dog", "Gregg"]
PERSISTING DATA
ADDING CHATTERS
client.on('join', function(name){
app.js
notify other clients a chatter has joined add name to chatters set
client.broadcast.emit("add chatter", name);
redisClient.sadd("chatters", name); });
server.on('add chatter', insertChatter);
index.html
var insertChatter = function(name) { var chatter = $('<li>'+name+'</li>').data('name', name); $('#chatters').append(chatter); }
PERSISTING DATA
ADDING CHATTERS (CONT)
client.on('join', function(name){
notify other clients a chatter has joined
client.broadcast.emit("add chatter", name);
app.js
redisClient.smembers('names', function(err, names) { names.forEach(function(name){ client.emit('add chatter', name); }); });
emit all the currently logged in chatters to the newly connected client
redisClient.sadd("chatters", name); });
add name to chatters set
PERSISTING DATA
REMOVING CHATTERS
remove chatter when they disconnect from server
client.on('disconnect', function(name){
app.js
client.get('nickname', function(err, name){ client.broadcast.emit("remove chatter", name); redisClient.srem("chatters", name); }); }); server.on('remove chatter', removeChatter);
index.html
var removeChatter = function(name) { $('#chatters li[data-name=' + name + ']').remove(); }
PERSISTING DATA
WELCOME TO CHATTR
PERSISTING DATA