View Source

The Art of I/O (2nd Session of 4)

A magical introduction to input and output signals

Previous Session | Course Home | Toggle Notes Mode | Watch Video Presentation | http://ioschool.is | Next Session

Course Recap

In our last session, we touched on

  • functions (accept input, return output, maybe do effects)
  • closures (functions that 'close over' variables)
  • callbacks (functions that are 'called back' later)
  • modules (exported code we can require in other code)

In this session, we'll dive deeper into the above and touch on

  • events
  • streams

We'll culminate with a fun cat app.

Hello Server

// hello-server.js

var http = require('http') // built-in module

function handler (req, res) {
  res.end("hello world!")
}

var server = http.createServer(handler)

server.listen(5000)

Start with node hello-server.js.

HTTP Magic

Request In, Response Out.

HTTP is a simple message passing protocol: the "client" sends a request message to the "server", the "server" sends a response message back to the "client."

Try being an HTTP client with your web browser, curl, or even netcat.

Random Cat Face

npm install cat-ascii-faces

// cat-face.js

var http = require('http') // built-in module
var catFaces = require('cat-ascii-faces') // npmjs.org module

function handler (req, res) {
  res.end(catFaces())
}

var server = http.createServer(handler)

server.listen(5000)

Start with node cat-face.js.

Random Cat API

npm install cat-names

// cat-api.js

var http = require('http') // built-in module
var catFaces = require('cat-ascii-faces') // npmjs.org module
var catNames = require('cat-names').random // npmjs.org module

function handler (req, res) {
  res.end(JSON.stringify({
    face: catFaces(),
    name: catNames(),
  }, null, 2))
}

http.createServer(handler).listen(5000)

Start with node cat-api.js.

Random Cat Images

npm install request

// cat-images.js

// we want to write a module that
// fetches a url to a random cat image
// and returns it via a callback
var request = require('request') // npmjs.org module

function catImages (callback) {
  var url = "http://random.cat/meow"
  request(url, function (err, res) {
    callback(err, res.body)
  })
}

module.exports = catImages

Import with require('./cat-images').

// cat-api.js

var http = require('http') // built-in module
var catFaces = require('cat-ascii-faces') // npmjs.org module
var catNames = require('cat-names').random // npmjs.org module
var catImages = require('./cat-images') // our shiny module

function handler (req, res) {
  catImages(function (err, catImage) {
    res.end(JSON.stringify({
      face: catFaces(),
      name: catNames(),
      image: catImage,
    }, null, 2))
  }))
}

http.createServer(handler).listen(5000)

Start with node cat-api.js.

Let's export our Cat API as a module, running the server if called directly.

// cat-api.js

// ...

module.exports = handler

if (!module.parent) {
  http.createServer(handler).listen(5000) 
}

Start with node cat-api.js.

Import with require('./cat-api.js').

Cat Server

Now let's write a higher-level handler that uses our ./cat-api handler when we request '/meow'.

// cat-server.js

var http = require('http')
var catApi = require('./cat-api')

function handler (req, res) {
  if (req.url === "/meow") {
    catApi(req, res)
  } else {
    res.writeHead(404, 'not found')
    res.end()
  }
}

http.createServer(handler).listen(5000)

Cat HTML

Now let's use our cat server to serve some HTML.

// cat-server.js

var http = require('http')
var fs = require('fs')
var catApi = require('./cat-api')

function handler (req, res) {
  if (req.url === "/meow") {
    catApi(req, res)
  } else if (req.url === "/") {
    fs.createReadStream("./cat-index.html").pipe(res)
  } else {
    res.writeHead(404, 'not found')
    res.end()
  }
}

http.createServer(handler).listen(5000)

Cat HTML

Now let's write that HTML.

<!-- cat-index.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>(=^.^=)</title>
    <link href="./styles.css" rel="stylesheet" />
  </head>
  <body>
    <main>
      <h1>Meow :3</h1>
      <div class="cats"></div>
    </main>
    <script src="./scripts.js"></script>
  </body>
</html>

Styles

Let's serve up some './styles.css'

// cat-server.js
// ...
function handler (req, res) {
// ...
  } else if (req.url === "/styles.css") {
    fs.createReadStream("./cat-styles.css").pipe(res)
  } else {
// ...
 cat-styles.css
main {
  text-align: center;
}
h1 {
  font-size: 100px;
}

Scripts

Let's serve up some './scripts.js'

// cat-server.js
// ...
function handler (req, res) {
// ...
  } else if (req.url === "/scripts.js") {
    fs.createReadStream("./cat-scripts.js").pipe(res)
  } else {
// ...
// cat-scripts.js

console.log("eomay")

Browserify Magic

browserify lets you require('modules') in the browser.

npm install browserify

// cat-server.js
// ...
var browserify = require('browserify')
// ...
function handler (req, res) {
// ...
  } else if (req.url === "/scripts.js") {
    browserify("./cat-scripts.js").bundle().pipe(res)
  } else {
// ...
// cat-scripts.js

console.log(require('cat-ascii-faces')())

Request a cat in the browser!

npm install xhr

// cat-scripts.js

var xhr = require('xhr')

function getCat (callback) {
  xhr("/meow", function (err, res) {
    callback(err, JSON.parse(res.body))
  })
}

getCat(function (err, cat) {
  if (!err) console.log(cat)
})

Display a cat in the browser!

npm install domquery

// cat-scripts.js -------------------------
// ...
var dom = require('domquery')

function addCat (cat) {
  var catView = 
    "<div class='cat'>" +
      "<h2>{face}</h2>" +
      "<h3>{name}</h3>" +
      "<img src='{image}'/>" +
    "</div>"

  dom('.cats').add(catView, cat)
}

// ...

getCat(function (err, cat) {
  if (!err) addCat(cat)
})

Cat Button

Let's add a cat button to our html.

<!-- cat-index.html -->
<main>
  <h1>Meow :3</h1>
  <div class="cats"></div>
  <button class="add-cat">Add Cat!</button>
</main>
// cat-scripts.js
// ...
dom('.add-cat').on('click', function (ev) {
  console.log(ev)
})

More Cats

Let's put it all together, add a cat when we click the button!

// cat-scripts.js
// ...
dom('.add-cat').on('click', function (ev) {
  getCat(function (err, cat) {
    if (!err) addCat(cat)
  })
})

Hey look, a sweet cat app! :3

Continuing the Magic

NodeSchool

More Advanced Magic

go on if you dare.

the rest of the slides did not fit within the presentation, but they are still available as notes.

Follow Your Dreams

It does not matter how or frivolous a project seems: everything you do adds to your body of work.

I can’t stress this enough: you are not just creating a bunch of small things. You are creating an ecosystem of projects.

- "Thoughts on small projects"

Messaging Magic

Messages in cyberspace are very similar to messages in meatspace.

Messages have headers that describe meta-data about the message, then a body with the contents of the message.

HTTP Request Crafting

GET /path/to/thing HTTP/1.1
Host: localhost:5000
User-Agent: dinosaur.is
X-Is-Magic: true
Content-Length: 15

here's some data

Try connecting to one of our servers with ncat localhost 5000 and pasting the above.

Inspection Server

// inspect-server.js

var http = require('http') // built-in module

function handler (req, res) {
  res.write("method: " + req.method + "\n")
  res.write("url: " + req.url + "\n")
  res.write("httpVersion: " + req.httpVersion + "\n")

  res.write("headers:\n")
  Object.keys(req.headers).forEach(function (name) {
    res.write("  " + name + ": " + req.headers[name] + "\n")
  })

  res.write("data: ")
  req.setEncoding('utf8')
  req.on('data', function (chunk) {
    res.write(chunk)
  })
  req.on('end', function () {
    res.end()
  })
}

var server = http.createServer(handler)

server.listen(5000)

Start with node inspect-server.js.

Echo Server

// echo-server.js

var http = require('http') // built-in module

function handler (req, res) {
    req.pipe(res)
}

var server = http.createServer(handler)

server.listen(5000)

Start with node server-echo.js.

Echo Client

// echo-client.js

var http = require('http') // built-in module

var path = "http://localhost/"

function handler (res) {
  var req = http.get(path, handler)
  res.pipe(req)
}

http.get(path, handler)

Static Server

// static.js

var http = require('http') // built-in module

function handler (req, res) {
  fs.readFile("." + req.url, function (err, contents) {
    res.write(contents)
    res.end()
  })
}

var server = http.createServer(handler)

server.listen(5000)

Sources