Code School Real Time Web With Nodejs
Code School Real Time Web With Nodejs
JS
- LEV EL O NE -
WHAT IS NODE.JS?
Allows you to build scalable network
applications using JavaScript on the server-side.
Node.js
V8 JavaScript Runtime
INTRO TO NODE.JS
WHAT COULD YOU BUILD?
• Websocket Server Like a c hat server
• Fast File Upload Client
• Ad Server
• Any Real-Time Data Apps
INTRO TO NODE.JS
WHAT IS NODE.JS NOT?
• A Web Framework
• For Beginners It’s very low level
• Multi-threaded
s in g le t h rea de d ser ver
You can think of it as a
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 you’re 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');
unt il c o mp lete
console.log(contents); Stop proc es s
console.log('Doing something else');
• 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 5s 10s
non-blocking
0s 5s 10s
INTRO TO NODE.JS
NODE.JS HELLO DOG
hello.js
var http = require('http'); How we require modules
http.createServer(function(request, response) {
response.writeHead(200); 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
INTRO TO NODE.JS
THE EVENT LOOP
Event Queue Known Events
Checking request
close for connection
request Events close
http.createServer(function(request, response) {
response.writeHead(200);
response.write("Dog is running.");
setTimeout(function(){ Represent long running process
response.write("Dog is done.");
response.end();
}, 5000); 5000ms = 5 seconds
}).listen(8080);
INTRO TO NODE.JS
TWO CALLBACKS HERE
var http = require('http');
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
Wasted Time
Request comes in
Request Callback executes
0s 5s 10s
TYPICAL BLOCKING THINGS
• Calls out to web services
• Reads/Writes on the Database
• Calls to extensions
INTRO TO NODE.JS
-
EVENTS
LE VE L TWO -
EVENTS IN THE DOM
The DOM triggers Events
you can listen for those events
The DOM click
submit events
attach hover
$("p").on("click",
! function(){ ... });
EVENTS
EVENTS IN NODE
Many objects in Node emit events
net.Server
request
EventEmitter
event
fs.readStream
data
EventEmitter
event
EVENTS
CUSTOM EVENT EMITTERS
var EventEmitter = require('events').EventEmitter;
events
var logger = new EventEmitter(); error warn info
logger.on('error', function(message){
console.log('ERR: ' + message);
});
listen for error event
logger.emit('error', 'Spilled Milk');
EVENTS
EVENTS IN NODE
Many objects in Node emit events
net.Server emit request
EventEmitter
event
attach
function(request,
! response){ .. }
EVENTS
HTTP ECHO SERVER
http.createServer(function(request,
! response){ ... });
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){ ... });
EVENTS
STREAM S
- L EVEL TH REE -
WHAT ARE STREAMS?
Start Processing
Immediately
Streams can be readable, writeable, or both
STREAMS
STREAMING RESPONSE
readable stream writable stream
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);
STREAMS
HOW TO READ FROM THE REQUEST?
events
Readable Stream emit data
EventEmitter 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)
Hello on client
Kinda like on the command line
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);
uploaded!
STREAMS
THE AWESOME STREAMING
server
client storage
original file
transferred file
non-blocking
0s 5s 10s
BACK PRESSURE!
server
Writable stream slower
than readable stream
client storage
original file
transferred file
All encapsulated in
readStream.pipe(writeStream);
FILE UPLOADING PROGRESS
STREAMS
FILE UPLOADING PROGRESS
$ curl --upload-file file.jpg http://localhost:8080
Outputs:
progress: 3%
progress: 6% • HTTP Server
progress: 9% We’re going to need:
progress: 12% • File System
progress: 13%
...
progress: 99%
progress: 100%
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 UL ES
- LEV EL F OUR -
REQUIRING MODULES
var http = require('http'); http.js
var fs = require('fs'); fs.js
How does ‘require’ return the libraries?
How does it find these files?
MODULES
LETS CREATE OUR OWN MODULE
custom_hello.js custom_goodbye.js
var hello = function() { exports.goodbye = function() {
console.log("hello!"); console.log("bye!");
} }
exports = hello;
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'
}
MODULES
ENCAPSULATING THE FUNCTION
var http = require('http'); app.js
var makeRequest = function(message) {
var options = {
host: 'localhost', port: 8080, path: '/', method: 'POST'
}
MODULES
CREATING & USING A MODULE
var http = require('http'); make_request.js
var makeRequest = function(message) {
...
}
exports = makeRequest;
MODULES
REQUIRE SEARCH
var make_request = require('./make_request') look in same directory
var make_request = require('../make_request') look in parent directory
var make_request = require('/Users/eric/nodes/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
In /Home/my_app/app.js
var request = require('request');
MODULES
LOCAL VS GLOBAL
Install modules with executables globally
$ npm install coffee-script -g global
$ coffee app.coffee
MODULES
FINDING MODULES
npm registry
npm command line
toolbox.no.de
github search
MODULES
DEFINING YOUR DEPENDENCIES
my_app/package.json
{ version number
"name": "My App",
"version": "1",
"dependencies": {
"connect": "1.8.7"
}
}
$ npm install
MODULES
DEPENDENCIES
my_app/package.json
"dependencies": {
"connect": "1.8.7" No conflicting modules!
}
Installs sub-dependencies
my_app node_modules connect
connect node_modules qs
MODULES
SEMANTIC VERSIONING
Major Minor Patch
"connect": "1.8.7" 1 . 8 . 7
Ranges
"connect": "~1" >=1.0.0 <2.0.0 Dangerous
"connect": "~1.8" >=1.8 <2.0.0 API could change
"connect": "~1.8.7" >=1.8.7 <1.9.0 Considered safe
http://semver.org/
MODULES
EXPRESS
- L EVEL FIVE -
EXPRESS
“Sinatra inspired web development framework for Node.js --
insanely fast, flexible, and simple”
EXPRESS
INTRODUCING EXPRESS
$ npm install express
var express = require('express');
root route
app.get('/', function(request, response) {
response.sendfile(__dirname + "/index.html");
}); current directory
app.listen(8080);
$ curl http://localhost:8080/
> 200 OK
EXPRESS
EXPRESS ROUTES
var request = require('request'); app.js
var url = require('url');
route definition
app.get('/tweets/:username', function(req, response) {
options = {
protocol: "http:", get the last 10 tweets for screen_name
host: 'api.twitter.com',
pathname: '/1/statuses/user_timeline.json',
query: { screen_name: username, count: 10}
}
EXPRESS
EXPRESS + HTML
EXPRESS
EXPRESS TEMPLATES
app.get('/tweets/:username', function(req, response) { app.js
...
request(url, function(err, res, body) {
var tweets = JSON.parse(body);
response.render('tweets.ejs', {tweets: tweets, name: username});
});
});
EXPRESS
EXPRESS TEMPLATES
EXPRESS
TEMPLATE LAYOUTS
<h1>Tweets for @<%= name %></h1> tweets.ejs
<ul>
<% tweets.forEach(function(tweet){ %>
<li><%= tweet.text %></li>
<% }); %>
</ul>
EXPRESS
EXPRESS TEMPLATES
EXPRESS
SOCKET.IO
- L EVEL S IX -
CHATTR
S O C K E T. I O
WEBSOCKETS
S O C K E T. I O
WEBSOCKETS
browser socket.io
Using duplexed websocket connection
S O C K E T. I O
SOCKET.IO FOR WEBSOCKETS
Abstracts websockets with fallbacks
$ npm install socket.io
io.sockets.on('connection', function(client) {
console.log('Client connected...');
});
S O C K E T. I O
SENDING MESSAGES TO CLIENT
io.sockets.on('connection', function(client) { app.js
console.log('Client connected...');
S O C K E T. I O
CHATTR HELLO WORLD
S O C K E T. I O
SENDING MESSAGES TO SERVER
io.sockets.on('connection', function(client) { app.js
client.on('messages', function (data) {
console.log(data);
}); listen for ‘messages’ events
});
<script>
var server = io.connect('http://localhost:8080');
index.html
$('#chat_form').submit(function(e){
var message = $('#chat_input').val();
emit the ‘messages’ event on the server
socket.emit('messages', message);
});
</script>
S O C K E T. I O
CHATTR HELLO WORLD
S O C K E T. I O
BROADCASTING MESSAGES
app.js
!
clients socket.broadcast.emit("message", 'Hello');
server
S O C K E T. I O
BROADCASTING MESSAGES
io.sockets.on('connection', function(client) { app.js
client.on('messages', function (data) {
client.broadcast.emit("messages", data);
});
broadcast message to all other clients connected
});
<script>
index.html
...
server.on('messages', function(data) { insertMessage(data) });
</script> insert message into the chat
S O C K E T. I O
BROADCASTING MESSAGES
S O C K E T. I O
SAVING DATA ON THE SOCKET
io.sockets.on('connection', function(client) { app.js
client.on('join', function(name) {
client.set('nickname', name); set the nickname associated
});
});
with this client
<script>
var server = io.connect('http://localhost:8080');
index.html
server.on('connect', function(data) {
$('#status').html('Connected to chattr');
nickname = prompt("What is your nickname?");
S O C K E T. I O
SAVING DATA ON THE CLIENT
io.sockets.on('connection', function(client) { app.js
client.on('join', function(name) {
client.set('nickname', name); set the nickname associated
});
with this client
client.on('messages', function(data){
get the nickname of this client before broadcasting message
client.get('nickname', function(err, name) {
client.broadcast.emit("chat", name + ": " + message);
}); broadcast with the name and message
});
});
S O C K E T. I O
SAVING DATA ON THE CLIENT
S O C K E T. I O
PERSIST ING DATA
- LEV EL S EVEN -
RECENT MESSAGES
PERSISTING DATA
RECENT MESSAGES
io.sockets.on('connection', function(client) { app.js
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);
});
});
});
PERSISTING DATA
STORING MESSAGES
var messages = []; store messages in array app.js
var storeMessage = function(name, data){
messages.push({name: name, data: data}); add message to end of array
if (messages.length > 10) {
messages.shift(); 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) { app.js
...
client.on('join', function(name) {
messages.forEach(function(message) {
client.emit("messages", message.name + ": " + message.data);
}); iterate through messages array
}); and emit a message on the connecting
}); client for each one
PERSISTING DATA
RECENT MESSAGES
PERSISTING DATA
PERSISTING STORES
• MongoDB All non-blocking!
• CouchDB
• PostgreSQL
• Memcached
• Riak
PERSISTING DATA
REDIS COMMAND DOCUMENTATION
PERSISTING DATA
NODE REDIS
PERSISTING DATA
REDIS
$ npm install redis
key value
client.get("message1", function(err, reply){
console.log(reply); "hello, yes this is dog"
});
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
PERSISTING DATA
CONVERTING MESSAGES TO REDIS
var storeMessage = function(name, data){ app.js
messages.push({name: name, data: data});
PERSISTING DATA
CONVERTING STOREMESSAGE
var redisClient = redis.createClient(); app.js
var storeMessage = function(name, data){
var message = JSON.stringify({name: name, data: data});
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) { app.js
messages.forEach(function(message) {
client.emit("messages", message.name + ": " + message.data);
});
});
PERSISTING DATA
OUTPUT FROM LIST
app.js
client.on('join', function(name) {
redisClient.lrange("messages", 0, -1, function(err, messages){
messages = messages.reverse(); reverse so they are emitted
in correct order
messages.forEach(function(message) {
message = JSON.parse(message); parse into JSON object
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");
["Dog", "Gregg"]
PERSISTING DATA
ADDING CHATTERS
client.on('join', function(name){ app.js
notify other clients a chatter has joined
client.broadcast.emit("add chatter", name);
redisClient.sadd("chatters", name);
}); add name to chatters set
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){ app.js
notify other clients a chatter has joined
client.broadcast.emit("add chatter", name);
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);
});
});
PERSISTING DATA
WELCOME TO CHATTR
PERSISTING DATA