Getting Started with Web Services Using Node and Express (for Cocoa developers)

(See Getting Started if you haven’t already.)

Let’s create an API. I could do something with databases — because that’s probably what you really need — but to keep things simple I’ll leave databases out of it. (I might get to them in a future post.)

Don’t Use Node Without It

You need Express. There are a ton of packages for Node — it’s a very active community. Express is a web app framework that takes care of a bunch of common things you’d otherwise have to write yourself. (If Node is Foundation, Express is UIKit or AppKit. Roughly.)

Node’s package manager npm makes installing packages easy. I believe it’s installed with Node, so you should already have it.

New App

Create a folder called expressdemo.

In that folder create a file called package.json. It’s like a Cocoa app’s Info.plist — it contains some basic information about your app, including its name, description, version, and a list of Node packages it depends on.

The file should look like this:

{
  "name": "expressdemo",
  "description": "Checking out Express",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "express": "3.x"
  }
}

If you’re not already in Terminal, open it up and navigate to that folder. Then type:

npm install

This will read the package.json file and install Express and any additional dependencies in a node_modules folder inside the expressdemo folder. (This keeps your app self-contained. You don’t have to wonder if the system has the right modules installed; you don’t have to wonder if the system has the right versions of various modules installed.)

Design the API

Our API does two simple things:

  1. Returns the current date in a JSON dictionary.

  2. Returns the base64-encoded version of a string we pass to it.

We want URLs like this:

/now - returns date
/base64?s=someString - returns base64 of someString

Create the Server

Inside the expressdemo folder create server.js with these lines at the top:

var express = require('express');
var http = require('http');
var app = express();
app.set('port', process.env.PORT || 1337);

Our app requires the express and http modules, and it creates an app by calling express(). It sets the port for listening from an environment variable — but, if that hasn’t been set, it uses 1337.

(Why an environment variable? Because the port shouldn’t be hard-coded. However, it’s useful to have a fallback for when you’re running locally, as we are now.)

Then add the code that starts the server:

http.createServer(app)­.listen(app.get('port'), function() {
  console.log('Express server listening on port ' + app.get('port'));
});

Add the API Code

We have two endpoints to respond to. Both use the GET method. Add the function for /now.

app.get('/now', function(request, response) {
  var d = new Date();
  response.send(200, {date: d});
});

Note the response.send parameters. The first parameter is the HTTP status code, and the second is the response body. Since the response body is a JavaScript dictionary, the response is returned as JSON. (That’s how easy returning JSON is.)

Then let’s add the /base64 function:

app.get('/base64', function(request, response) {
  var stringToEncode = request.query.s;
  var base64EncodedString = new Buffer(stringToEncode, 'utf8').toString('base64');
  response.send(200, base64EncodedString);
});

This time the response body is a string, so it’s returned as itself rather than as JSON.

Note that we got the string to encode via the request.query dictionary.

Save server.js and run it from Terminal: node server.js

Testing

Open a new tab in Terminal and try a few things:

curl -I http://localhost:1337/

The result should be something like this:

HTTP/1.1 404 Not Found
X-Powered-By: Express
Content-Type: text/html
Date: Tue, 10 Dec 2013 05:07:26 GMT
Connection: keep-alive

(The -I switch shows just the response headers.)

curl -I http://localhost:1337/now

The /now endpoint returns the date in a JSON dictionary. The Content-Type header should specify JSON — even though our code didn’t specify that explicitly — and it does:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 40
Date: Tue, 10 Dec 2013 05:09:07 GMT
Connection: keep-alive

The /base64 endpoint does not return JSON, and the Content-Type header should specify text. And it does:

curl -I 'http://localhost:1337/base64?s=foo'

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 4
Date: Tue, 10 Dec 2013 05:10:43 GMT
Connection: keep-alive

The /now endpoint should actually return JSON with something that looks like a date:

curl http://localhost:1337/now

{
  "date": "2013-12-10T05:13:21.772Z"
}

The base64 endpoint should return the input string as base64. We know that foo encodes as Zm9v.

curl 'http://localhost:1337/base64?s=foo'

Zm9v

It works!

Notes

That wasn’t a bunch of code, and you’ve got two endpoints, and one even returns results as JSON. So easy.

Your next step might be to write some unit tests. For that I recommend Mocha.

09 Dec 2013

Archive