Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
The script from the tech talk I gave at SkillSwap weekend
JavaScript
Branch: master

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
examples
slides
README.markdown

README.markdown

NodeJS Tech Talk

These are the slides, examples, and script from the talk I gave at SkillSwap weekend at Carnegie Mellon. This is a very introductory talk, aimed at teaching students the basics of node and event-orientated programming in general. There's also a frequent amount of swearing.

If you don't have keynote, the slides are available for viewing on SlideShare.

Catman

Let's start off with a really BAD example. Everyone always tries to impress people. Let me show you where node really challenges your fundamental way of thinking.

We are going to write a program called catman that takes an arbitrary number of arguments and cats their contents together. i.e. we are writing the linux cat command in Node.

You'll probably want to start out with something like this:

var fs = require('fs');

// grab the arguments
var args = process.argv.splice(2);

for(var i = 0; i < args.length; i++) {
  var filename = args[i];
  fs.readFile(filename, 'utf8', function(error, data) {
    if(error) throw error;
    console.log(data);
  });
}

A large problem here is that this method is asynchronous. The cat command expects that the files be concatenated together in the order in which they are entered. This is not the case here. In this example, longer files will always be at the end, because they take longer to return.

While there exists a readFileSync() method that behaves like you'd expect, how would you write this behavior asynchronously or in an event-driven way?

Recursion for the win!

var fs = require('fs');

// grab the arguments
var filenames = process.argv.splice(2);
readFile(0);

function readFile(index) {
  if(index < filenames.length) {
    var filename = filenames[index];

    fs.readFile(filename, 'utf8', function(error, data) {
      if(error) throw error;
      console.log(data);

      readFile(index+1);
    })
  }
}

A seemingly simple task is actually somewhat more complex.

The Annoying 5 year old

More commonly known as the "Echo Server", this example demonstrates how easy it is to create a simple server that echos back a given request.

var net = require('net');

var server = net.createServer(function (socket) {
  socket.write("Hi, I'm Chase!\n");
  socket.pipe(socket);
});

server.listen(1234, '127.0.0.1');

For the record, Chase one of my two little brothers. He was never actually annoying.

To run this code, save it to a file and run it using the node command:

$ node annoying-five-year-old.js

If you telnet into the server and type some things, you should see output like this:

$ telnet 127.0.0.1 1234
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hi, I'm Chase!
Hi Chase
Hi Chase
Hey, stop mocking me
Hey, stop mocking me
HEY!
HEY!

This is great, but it's hard to tell who is doing the mocking here. We should prefix Chase's mocks with "Chase:". You might try something like this:

var net = require('net');

var server = net.createServer(function (socket) {
  socket.write("Hi, I'm Chase!\n");
  socket.pipe("Chase: " + socket);
});

server.listen(1234, '127.0.0.1');

But you'll quickly see that fail. Why? Before we were essentially piping the socket into itself. But we can't concatenate a string to a stream... That doesn't make much sense! Instead, we actually need to listen for the data event:

var net = require('net');

var server = net.createServer(function (socket) {
  socket.write("Hi, I'm Chase!\n");

  socket.on('data', function(data) {
    socket.write("Chase: " + data);
  });
});

server.listen(1234, '127.0.0.1');

Here, we listen for the data, prefix with "Chase: ", and then write the data back to the socket. Notice that we are using socket.write and not socket.pipe. We are no longer streaming output directly.

TCP Chat

TCP Chat is a very simple implementation using node. Using what we learned from the annoying five year old, we can actually broadcast messages instead of just repeating them.

First, we need to require the net module and create a data structure to keep track of all our clients.

var net = require('net');

var clients = []; // array for holding our clients

Next, we'll want to create a server:

net.createServer(function(socket) {
  socket.name = socket.getAddress();
  clients.push(socket);
});

Notice that we also set a property on the socket called name. Right now, it's just the remoteAddress(), but you could prompt for a username or use another identifier.

Arguably the more complicated part is writing the broadcast method. It's actually fairly simple though:

function broadcast(message, sender) {
  clients.forEach(function (client) {
    if(client !== sender) {
      client.write(message);
    }
  });

  process.stdout.write(message);
}

Basically, iterate over each client and write to that socket (unless the socket belongs to the sender).

Lastly, just put together what we learned from the last example:

var net = require('net');

var clients = [];

var server = net.createServer(function(socket) {
  socket.name = socket.remoteAddress
  clients.push(socket);

  socket.write('Welcome to chat ' + socket.name + "\n");
  broadcast('[SYSTEM] ' + socket.name + " joined the chat\n");

  socket.on('data', function(data) {
    broadcast(socket.name + ': ' + data, socket);
  });

  socket.on('end', function() {
    clients.splice(clients.indexOf(socket), 1);
    broadcast('[SYSTEM] ' + socket.name + " left the chat.\n");
  })
});

server.listen(3000);

function broadcast(message, sender) {
  clients.forEach(function (client) {
    if(client !== sender) {
      client.write(message);
    }
  });

  process.stdout.write(message);
}
Something went wrong with that request. Please try again.