Monthly Archives: December 2014

Very simple REST JSON node.js server

I want to build a modern web application (perhaps using AngularJS) or some mobile application, and I need to have some working server side to get started. There are of course plenty of options; .NET WebApi, LAMP, MongoDB, NodeJS + Express, and many more. But I want it stupid simple. This is tested on Linux, but everything should apply in Windows too.

I wrote a very simple REST/JSON server for node.js, and this is about it (source code in the end).

How to run it
Presuming you have nodejs installed:

$ node simple-rest-server.js

It now listens to port 11337 on 127.0.0.1 (that is hard coded in the code).

Configure with Apache
The problem with port 11337 is that if you build a web application you will get cross site problems if the service runs on a different port than the html files. If you are running apache, you can:

# a2enmod proxy
# a2enmod proxy_http

Add to /etc/apache/sites-enabled/{your default site, or other site}
ProxyPass /nodejs http://localhost:11337
ProxyPassReverse /nodejs http://localhost:11337

# service apache2 restart

You can do this with nginx too, and probably also with IIS.

Use from command line
Assuming you have a json data file (data99.json) you can write to (POST), read from (GET) and delete from (DELETE) the server:

$ curl --data @data99.json http://localhost/nodejs/99
$ curl http://localhost/nodejs/99
$ curl -X DELETE http://localhost/nodejs/99

If you did not configure apache as proxy as suggested above, you need to us the :port instead of /nodejs. In this case 99 is the document id (a positive number). You can add any number of documents with whatever ids you like (as long as they are positive numbers, and as long as the server does not run out of memory). There is no list function in this very simple server (although it would be very easy to add).

Using from AngularJS
The command line is not so much fun, but AngularJS is. If you create your controller with $http the following works:

function myController($scope, $http) {

  // write an object named x with id
  h = $http.post('http://localhost/nodejs/' + id, x)
  h.error(function(r) {
    // your error handling (may use r.error to get error message)
  })
  h.success(function(r)) {
    // your success handling
  })

  // read object with id to variable x
  h = $http.get('http://localhost/nodejs/' + id)
  h.error(function(r) {
    // your error handling
  })
  h.success(function(r) {
    x = r.data
  })

  // delete object with id 
  h = $http['delete']('http://localhost/nodejs/' + id)
  h.error(function(r) {
    // your error handling
  })
  h.success(function(r)) {
    // your success handling
  })
}

I found that Internet Explorer can have problems with $http.delete, thus $http[‘delete’] (very pretty).

What the server also does
The server handles GET, POST and DELETE. It validates and error handles its input (correctly, I think). It stores the data to a file, so you can stop/start the server without losing information.

What the server does not do
In case you want to go from prototyping to production, or you want more features, it is rather simple to:

  1. add function to list objects
  2. add different types of objects
  3. let the server also serve files such as .html and .js files
  4. use MongoDB as backend
  5. add security and authentication

The code
The entire code follows (feel free to modify and use for your own purpose):

/*
 * A very simple JSON/REST server
 *
 * http://host:port/{id}       id is a positive number
 *
 * POST   - create/overwrite   $ curl --data @file.json http...
 * GET    - load               $ curl http...
 * DELETE - delete             $ curl -X DELETE http...
 *
 */
glHost    = { ip:'127.0.0.1', port:'11337' }
glHttp    = require('http')
glUrl     = require('url')
glFs      = require('fs')
glServer  = null
glStorage = null

/* Standard request handler - read all posted data before proceeding */
function requestHandler(req, res) {
  var pd = ""
  req.on("data", function(chunk) {
    pd += chunk
  })
  req.on("end", function() {
    requestHandlerWithData(req, res, pd)
  })
}

/* Custom request handler - posted data in a string */
function requestHandlerWithData(req, res, postdata) {
  var in_url  = glUrl.parse(req.url, true)
  var id      = in_url["pathname"].substring(1) //substring removes leading /
  var retcode = 200
  var retdata = null
  var error   = null

  if ( ! /^[1-9][0-9]*$/.test(id) ) {
    error   = "Invalid id=" + id
    retcode = 400
  }

  if ( ! error ) switch ( req.method ) {
  case "GET":
    if ( ! glStorage[id] ) {
      error = "No object stored with id=" + id
      retcode = 404
    } else {
      retdata = glStorage[id]
    }
    break; 
  case "POST":
    try {
      glStorage[id] = JSON.parse(postdata)
      writeStorage()
    } catch(e) {
      error = "Posted data was not valid JSON"
      retcode = 400
    }
    break;
  case "DELETE":
    delete glStorage[id]
    writeStorage()
    break;
  default:
    error   = "Invalid request method=" + req.method
    retcode = 400
    break;
  }

  res.writeHead(retcode, {"Server": "nodejs"})
  res.writeHead(retcode, {"Content-Type": "text/javascript;charset=utf-8"})
  res.write(JSON.stringify( { error:error, data:retdata } ))
  res.end()

  console.log("" + req.method + " id=" + id + ", " + retcode +
    ( error ? ( " Error=" + error ) : " Success" ) )
}

function writeStorage() {
  glFs.writeFile("./db.json",JSON.stringify(glStorage),function(err) {
    if (err) {
      console.log("Failed to write to db.json" + err)
    } else {
      console.log("Data written to db.json")
    }
  })
}

glFs.readFile("db.json", function(err, data) {
  if (err) {
    console.log("Failed to read data from db.json, create new empty storage")
    glStorage = new Object()
  } else {
    glStorage = JSON.parse(data)
  }
})
glServer = glHttp.createServer(requestHandler)
glServer.listen(glHost.port, glHost.ip)
console.log("Listening to http://" + glHost.ip + ":" + glHost.port + "/{id}")