Wednesday, February 13, 2013

a POST about node.js (get it?)

Very often in coding, you want to do something in a hurry, pick up some new whizbang piece of technology and just start using it without taking the time to read all the documentation. At my new job, that's what I decided to do with "node.js" to write a little RESTful server in a hurry.

node.js lets you write server code in javascript, and its event-driven structure has a reputation for being very fast and scalable. express.js is a common lightweight library for node to handle a lot of the server basics.

First step: install node.js.  (All these following specifics are for Macs) This is as simple as going to nodejs.org, hitting download, and running the .pkg. At this point I could run "node" from the terminal (ctrl-c twice to end) to see that it worked. 

I'd suggest running the first example there on the nodejs.org page; it's kind of mind blowing to get a real live server work with so little code or fuss!

In that example, though, you do see there is a little fuss, having to manually set Content-type ad what not. Express.js is key to taking care of that kind of thing, and dozens of other little details as you try to do more complex stuff. (To be honest, I found out about Express by googling something like "node read post" (I knew I was going to have to read POST data) and found this stackoverflow article- (amazing what a crucial resource that site is.))

You don't even have to explicitly download express -- you should let "npm", the "node package manager" that you got for free when you installed node.js, do the work. In fact, the first part of the express.js guide page has a hello world program that explains thing better than I would-- the idea is you make a "package.json" file that describes what your app will need, and then type "npm install", and it takes care of the rest. UPDATE: that page has kind of gone away. The getting started page kind of picks up from that... 

So if it's not clear from the guide, after you've done the package.json step, enter this as hello-world.js:
var express = require('express');
var app = express();
app.get('/hello.txt', function(req, res){
  res.send('Hello World');
});
app.listen(3000);
console.log('Listening on port 3000');
Then type "node hellow-world.js", and you should then be able to go to a browser and navigate to http://localhost:3000/hello.txt and see a nice greeting.

At this point, I would suggest grabbing nodemon. This little wonder saves you from having to break out and restart your server after every change to your js file. You can get it with npm, but you probably want to install it with the "-g" option so you can run it from the command line, and that likely means you want to run the command as Administrator via sudo.... so that's "sudo npm install nodemon -g" in all, and then you can type "node hello-world.js", and evey time you change the file (or another file in the directory) it restarts the server. This means you can get the "save file, switch to browser, hit reload" loop going, and quickly iterate as you develop your program -- POWERFUL STUFF.

So, with that out of the way, I had to address my main task: making a nice fake server to act as a testbed. I had to implement a RESTful state service. Two of the slight challenges for that (at least challenges in that they were beyond the vanilla "read a fixed URL, spit out a fixed response") were A. I have to cope with an identifier embedded in the URL, so I can't set up a fixed pattern for GETs and B. I need to be able to read a POST body that was JSON.

Both of these tasks were "more normal" these days than they were when I was last coding in server land, when every script or servlet had a unique URL and all extra information was CGI parameters, whether it was GET or POST. (I realize I'm taking a lot of things for granted as I describe this; I guess my blog isn't as newbie-friendly as I'd like it to be.) And besides the server stuff, I also had to make sure I knew the right jQuery to POST raw JSON.

To cut to the chase, the jQuery was pretty easy:

  $.ajax({
    url: "/foo",
    type: "post",
    data: {'first':"do no harm"},
    dataType: "json",
    success: function(response) {
        alert(JSON.stringify(response));
    },
    error: function(e){
        alert("ERROR\n\n"+JSON.stringify(e,null," "));
    }
});

(forgive the nonsense I use for data in these cases (first do no harm?) -- I just don't want to have to think about it much.)

My node code wasn't much harder... my "final" learning app does three things:

  1. if "/" is requested (i.e. the root of the site) it serves up a static index.html file (that's where I ended up putting a form with the above jQuery) That's what the "sendfile()" code is doing.
  2. if "/test/FOOBAR" is requested, where FOO can be pretty much anything, it makes a trivial page to say what was passed as the second part of the URL. The secret sauce there is using ":somename" in the url matcher which then makes the value available at req.params.somename 
  3. if "/foo" is POST'd to, the data is parsed (and it doesn't matter if it's CGI or JSON; express takes care of abstracting that detail out.) Here, that requires a piece of what express calls middleware, the line is "app.use(express.bodyParser());". (I don't quite undertand how multiple calls to .use work, but I had a problem because it didn't work until I moved app.use above the first app.get, not just before the first post.) Once that middleware is applied, the request then has a "body" key that is a map of the parameters passed in.
  4. My code also shows an example of using a "global" variable ("count")-- but that value only lasts as long as the server is running! If you restart (like because you just changed the app) it will be reset. (The Express Guide page's "Users online count" introduced me to the hash-key database Redis-- the running and installation of that was almost as easy as the rest of node put together, but that's a lesson for another post. Anyway, obviously that kind of db would let you preserve data across server restarts.)
Here is the code:



var express = require('express');
var app = express();
var count = 0;

app.use(express.bodyParser());

app.get('/', function (req, res) {
   res.sendfile(__dirname + '/index.html');
});

app.get("/test/:id",function(req,res){
    res.send(" I GOT "+req.params.id);
});
app.post("/foo",function(req,res){  
    console.log(req.body);
    res.send({"pow":"oops::"+req.body.first,"howmany":count++});
    
});
app.listen(3000);
console.log('Listening on port 3000');



This stuff is empowering! Furthering the empowerment, there's also a company called heroku that lets you install stuff on the interwebs for free. I watched a great O'Reilly Webcast by Peter Cooper, How to Build a Chat Room in JavaScript in Under an Hour that details both the node.js and the heroku aspect. I should probably watch that again now that I've done this today.

(Incidentally, Peter Cooper curates Javascript Weekly -- I remember at a new job in 2009 or so, my fellow coder showed me "prototype.js" -- besides the coolness of that language, I had to wonder where I could go to learn about what new stuff was out there! It can be a challenge learning about technologies that you don't happen to be using at work. A few years later this newsletter came out, and could have answered my question. If you're doing UI in a browser, you should get and read this newsletter.)

1 comment:

  1. A good primer on first steps. One thing I would add is that while it is OK to pass static files from node when doing a quick hack, the next thing you'll want to get going is some sort of reverse proxy to node.js and have static resources served from a more optimized place.

    ReplyDelete