Calling a REST backend service from an Express application

Now that we've seen how to make HTTP client requests, we can look at how to make a REST query inside an Express web application. What that effectively means is to make an HTTP GET request to a backend server, which responds with the Fibonacci number represented by the URL. To do so, we'll refactor the Fibonacci application to make a Fibonacci server that is called from the application. While this is overkill for calculating Fibonacci numbers, it lets us look at the basics of implementing a multi-tier application stack.

Inherently, calling a REST service is an asynchronous operation. That means calling the REST service will involve a function call to initiate the request and a callback function to receive the response. REST services are accessed over HTTP, so we'll use the HTTP client object to do so.

Implementing a simple REST server with Express

While Express has a powerful templating system, making it suitable for delivering HTML web pages to browsers, it can also be used to implement a simple REST service. The parameterized URL's we showed earlier (/user/profile/:id) can act like parameters to a REST call. And Express makes it easy to return data encoded in JSON.

Now, create a file named fiboserver.js containing this code:

var math  = require('./math');
var express = require('express');
var logger = require('morgan');
var app = express();
app.use(logger('dev'));
app.get('/fibonacci/:n', (req, res, next) => {
    math.fibonacciAsync(Math.floor(req.params.n), (err, val) => {
        if (err) next('FIBO SERVER ERROR ' + err);
        else {
            res.send({
                n: req.params.n,
                result: val
            });
        }
    });
});
app.listen(process.env.SERVERPORT);

This is a stripped-down Express application that gets right to the point of providing a Fibonacci calculation service. The one route it supports, handles the Fibonacci computation using the same functions we've already worked with.

This is the first time we've seen res.send used. It's a flexible way to send responses which can take an array of header values (for the HTTP response header), and an HTTP status code. As used here, it automatically detects the object, formats it as JSON text, and sends it with the correct Content-Type.

Then, in package.json, add this to the scripts section:

"server": "SERVERPORT=3002 node ./fiboserver"

This automates launching our Fibonacci service.

Note

Note that we're specifying the TCP/IP port via an environment variable and using that variable in the application. This is another aspect of the Twelve-Factor application model; to put configuration data in the environment.

Now, let's run it:

$ npm run server

> [email protected] server /Users/david/chap04/fibonacci
> SERVERPORT=3002 node ./fiboserver

Then, in a separate command window, we can use the curl program to make some requests against this service:

$ curl -f http://localhost:3002/fibonacci/10
{"n":"10","result":55}
$ curl -f http://localhost:3002/fibonacci/11
{"n":"11","result":89}
$ curl -f http://localhost:3002/fibonacci/12
{"n":"12","result":144}

Over in the window where the service is running, we'll see a log of GET requests and how long each took to process.

Now, let's create a simple client program, fiboclient.js, to programmatically call the Fibonacci service:

var http = require('http');
var util = require('util');
[
  "/fibonacci/30", "/fibonacci/20", "/fibonacci/10",
  "/fibonacci/9", "/fibonacci/8", "/fibonacci/7",
  "/fibonacci/6", "/fibonacci/5", "/fibonacci/4",
  "/fibonacci/3", "/fibonacci/2", "/fibonacci/1"
].forEach(path => {
    util.log('requesting ' + path);
    var req = http.request({
      host: "localhost",
      port: 3002,
      path: path,
      method: 'GET'
    }, res => {
      res.on('data', chunk => {
          util.log('BODY: ' + chunk);
      });
    });
    req.end();
});

Then, in package.json, add this to the scripts section:

"client": "node ./fiboclient"

Then run the client app:

$ npm run client

> [email protected] client /Users/david/chap04/fibonacci
> node ./fiboclient

31 Jan 16:37:48 - requesting /fibonacci/30
31 Jan 16:37:48 - requesting /fibonacci/20
31 Jan 16:37:48 - requesting /fibonacci/10
31 Jan 16:37:48 - requesting /fibonacci/9
31 Jan 16:37:48 - requesting /fibonacci/8
31 Jan 16:37:48 - requesting /fibonacci/7
31 Jan 16:37:48 - requesting /fibonacci/6
31 Jan 16:37:48 - requesting /fibonacci/5
31 Jan 16:37:48 - requesting /fibonacci/4
31 Jan 16:37:48 - requesting /fibonacci/3
31 Jan 16:37:48 - requesting /fibonacci/2
31 Jan 16:37:48 - requesting /fibonacci/1
31 Jan 16:37:48 - BODY: {"n":"2","result":1}
31 Jan 16:37:48 - BODY: {"n":"1","result":1}
31 Jan 16:37:48 - BODY: {"n":"3","result":2}
31 Jan 16:37:48 - BODY: {"n":"4","result":3}
31 Jan 16:37:48 - BODY: {"n":"5","result":5}
31 Jan 16:37:48 - BODY: {"n":"6","result":8}
31 Jan 16:37:48 - BODY: {"n":"7","result":13}
31 Jan 16:37:48 - BODY: {"n":"8","result":21}
31 Jan 16:37:48 - BODY: {"n":"9","result":34}
31 Jan 16:37:48 - BODY: {"n":"10","result":55}
31 Jan 16:37:48 - BODY: {"n":"20","result":6765}
31 Jan 16:37:59 - BODY: {"n":"30","result":832040}

We're building our way toward adding the REST service to the web application. At this point, we've proved several things, one of which is the ability to call the REST service and use the value as data in our program.

We also, inadvertently, demonstrated the issue with long-running calculations. You'll notice the requests were made from the largest to the smallest, but the results appeared in almost exactly the opposite order. Why? It's because of the processing time for each request, and the inefficient algorithm we're using. The computation time increases enough to ensure that the larger request values require enough time to reverse the order.

What happened is that fiboclient.js sends all its requests right away, and then each one goes into a wait for the response to arrive. Because the server is using fibonacciAsync, it will work on calculating all responses simultaneously. The values which are quickest to calculate are the ones which will be ready first. As the responses arrive in the client, the matching response handler fires, and in this case, the result is printed to the console. The results will arrive when they're ready and not a millisecond sooner.

Refactoring the Fibonacci application for REST

Now that we've implemented a REST-based server, we can return to the Fibonacci application, applying what you've learned to improve it. We will lift some of the code from fiboclient.js and transplant it into the application to do this. Change routes/fibonacci.js to the following code:

router.get('/', function(req, res, next) {
  if (req.query.fibonum) {
    var httpreq = require('http').request({
      host: "localhost",
      port: process.env.SERVERPORT,
      path: "/fibonacci/"+Math.floor(req.query.fibonum),
      method: 'GET'
    }, 
    httpresp => {
      httpresp.on('data', chunk => {
        var data = JSON.parse(chunk);
        res.render('fibonacci', {
          title: "Calculate Fibonacci numbers",
          fibonum: req.query.fibonum,
          fiboval: data.result
        });
      });
      httpresp.on('error', err => { next(err); });
    });
    httpreq.on('error', err => { next(err); });
    httpreq.end();
  } else {
    res.render('fibonacci', {
      title: "Calculate Fibonacci numbers",
      fiboval: undefined
    });
  }
});

module.exports = router;

Then, in package.json, change the scripts entry to the following:

"start": "SERVERPORT=3002 DEBUG=fibonacci:* node ./bin/www",

This way, we duplicate the same environmental configuration for running both the Fibonacci service and the Fibonacci application.

In one command window, start the server:

$ npm run server

> [email protected] server /Users/david/chap04/fibonacci
> SERVERPORT=3002 node ./fiboserver

In the other window, start the application:

$ npm start

> [email protected] start /Users/david/chap04/fibonacci
> SERVERPORT=3002 DEBUG=fibonacci:* node ./bin/www

  fibonacci:server Listening on port 3000 +0ms

Because we haven't changed the templates, the screen will look exactly as it did earlier.

We'll run into another problem with this solution. The asynchronous implementation of our inefficient Fibonacci algorithm will easily cause the Fibonacci service process to run out of memory. In the Node.js FAQ, https://github.com/nodejs/node/wiki/FAQ, it's suggested to use the --max_old_space_size flag. You'd add this in package.json as follows:

"server": "SERVERPORT=3002 node ./fiboserver --max_old_space_size 5000",

However, the FAQ also says that if you're running into maximum memory space problems, your application should probably be refactored. This gets back to our point several pages ago, that there are several approaches to addressing performance problems. One of which is algorithmic refactoring of your application.

Why did we go to this trouble when we could just directly call fibonacciAsync?

We can now push the CPU load for this heavyweight calculation to a separate server. Doing so would preserve CPU capacity on the frontend server so it can attend to web browsers. The heavy computation can be kept separate, and you can even deploy a cluster of backend servers sitting behind a load balancer evenly distributing requests. Decisions like this are made all the time to create multitier systems.

What we've demonstrated is that it's possible to implement simple multitier REST services in a few lines of Node.js and Express. The whole exercise gave us a chance to think about computationally intensive code in Node.js.

Some RESTful modules and frameworks

Here are a few available packages and frameworks to assist your REST-based projects:

  • Restify (http://restify.com/): This offers both client-side and server-side frameworks for both ends of REST transactions. The server-side API is similar to Express.
  • Loopback (https://strongloop.com/node-js/loopback-framework/): This is an offering from StrongLoop, the current sponsor of the Express project. It offers a lot of features and is, of course, built on top of Express.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset