Subscribe

Share

Application Development

Build REST APIs for Node.js, Part 1

Start by learning about web server basics.

By Dan McGhan

May/June 2018

Node.js and REST APIs go hand in hand. In fact, Ryan Dahl (the creator of Node.js) once described the focus of Node.js as “doing networking correctly.” But where should you start when building a REST API for Node.js? What components should be used, and how should things be organized? These are surprisingly difficult questions to answer—especially when you’re new to the Node.js ecosystem.

You could choose to use low-level packages and lots of custom code to build an API that’s highly optimized for a specific workload. Or you could use an all-in-one framework such as Sails, where many of the decisions have been made for you. There is no right or wrong answer—the best option will depend on the type of project you’re working on and where you want to invest your time.

In this article series, I’ll assume you’re new to Node.js and show you how to build a REST API that attempts to balance granular control and magical black boxes. The goal will be to create an API that supports basic create, read, update, and delete (CRUD) functionality on the EMPLOYEES table in the HR sample schema. By the end of this series, you should be able to make decisions about what’s best for your own projects.

This first article focuses on web server basics. Future articles will cover topics such as database basics (including connection pooling), handling various types of HTTP requests, and more.

Target Environment

For consistency, the instructions I use throughout the series will assume that you’re working with the Oracle Database developer VM and that Node.js version 8 or higher is installed in the VM. See this post for instructions on setting up this type of environment.

I generally prefer to run Node.js in my host OS and communicate with the database in the guest VM. You can adapt the instructions to do this if you want; just be aware that doing so will require additional installation steps to get the driver working on your platform.

High-Level Components

I like to organize my REST APIs into four core components or layers. Incoming HTTP requests will usually touch each of these layers in turn. There may be a lot more going on, depending on the features an API supports, but the following components are required:

  • Web server. The role of the web server is to accept incoming HTTP requests and send responses. There are many options for web servers in Node.js. At the lowest level, you could use the built-in modules, such as http, https, and http2. For most folks, those modules will be too low-level. I like to use Express for the web server, because it’s very popular, flexible, and easy to use. There are many other web server options, including restify, kracken, and hapi. You might consider some of these options as you experiment more with APIs over time.
  • Router. Routing logic is used to define the URL endpoints and HTTP methods the API will support. At runtime this layer will map incoming requests to the appropriate controller logic. The implementation of the routing logic is almost always tied to the choice of web server, because most include a means to define routes. I’ll use the Router class that comes with Express in this series.
  • Controllers. The controller layer includes one JavaScript function for each URL path/HTTP method combination. The function will inspect and pull data from the incoming request (URL, body, and HTTP headers) as needed, interact with appropriate database APIs to fetch or persist data, and then generate the HTTP response.
  • Database APIs. The database APIs will handle the interactions with the database. This layer will be isolated from the HTTP request and response. Some developers will prefer to use an object-relational mapper (ORM), such as Sequelize, to abstract away the database as much as possible. For those folks, this layer is often called the model layer, because ORMs work by defining models in the middle tier. I’m going to stay lower-level and work directly with the Oracle Database driver for Node.js (node-oracledb).

Starting Up and Saying Hello

The code in this project will be organized with a generic directory structure that can be adjusted and built out over time as needed.

With your setup—the Oracle Database developer VM and Node.js version 8 or higher installed in it—complete, open a new terminal by going to Applications -> Favorites -> Terminal and then run the following commands:

cd ~
mkdir hr_app
cd hr_app/
touch index.js
mkdir config
touch config/web-server.js
mkdir controllers
mkdir db_apis
mkdir services
touch services/web-server.js

The project will be contained in the hr_app directory. The directories within hr_app should be easy to understand, based on their names and the “High-Level Components” overview above. The index.js file can be thought of as the main file in a Java app—it will be the entry point of the application. You will be adding code to that file and the web-server.js files in the config and services directories in this article.

Go to Applications -> Favorites -> Files to open the file browser, and navigate to Home -> hr_app -> config. Double-click web-server.js to open it in the gedit text editor. Copy and paste the following code into the file, and save your changes.

module.exports = {
  port: process.env.HTTP_PORT || 3000
};

This is a simple JavaScript module that exposes a single property named port. In Node.js, the process object has an env property that contains the user environment. I’m using that to set the value of port to the value of the environment variable HTTP_PORT. If that environment variable isn’t defined, the default value will be 3000. It’s common to derive port values from environment variables, because they may vary in different environments or be randomly assigned at runtime.

With the configuration file ready, you can turn your attention to the web server module. Open the services/web-server.js file in gedit. Copy and paste the following code into the file, and save your changes.

 const http = require('http');
const express = require('express');
const webServerConfig = require('../config/web-server.js');

let httpServer;

function initialize() {
  return new Promise((resolve, reject) => {
    const app = express();
    httpServer = http.createServer(app);

    app.get('/', (req, res) => {
      res.end('Hello World!');
    });

    httpServer.listen(webServerConfig.port, err => {
      if (err) {
        reject(err);
        return;
      }

      console.log('Web server listening on localhost:${webServerConfig.port}');

      resolve();
    });
  });
}

module.exports.initialize = initialize;

Here’s a line-by-line breakdown of the web server module so far:

Lines Description
1–3 Several modules are required. The http module is included with Node.js, but the express module will need to be installed via npm.
7–27 A function named initialize is declared. The function immediately returns a promise, which is resolved or rejected, depending on whether the web server is started successfully.
9–10 A new Express application is created (which is really just a function) and then used to create an HTTP server via the http module.
12–14 The app’s get method is used to add a handler for GET requests that come in on the root (/) path. The callback function (also called a middleware function) will be invoked when such a request is received, and it will use the “response” parameter (res) to send a “Hello World!” response to the client.
16–25 The server’s listen method is used to bind to the specified port and start listening for incoming requests.
29 The initialize function is exported from the module so it can be invoked externally.

With the web server module defined, you can put it to use in the main module. Open index.js, copy and paste the following code into it, and save your changes.

const webServer = require('./services/web-server.js');

async function startup() {
  console.log('Starting application');

  try {
    console.log('Initializing web server module');

    await webServer.initialize();
  } catch (err) {
    console.error(err);

    process.exit(1); // Non-zero failure code
  }
}

startup();

The main module brings in the web server module, and then it defines and invokes an async function named startup. Because the web server module’s initialize function returns a promise, you can use it with async/await and wrap it in a try-catch block (follow this link to learn more about async/await). If the initialize function finishes successfully, the web server will be running; otherwise, any exceptions will be caught and handled.

All you need to do now is initialize npm and install Express—then you can run the app. Run the following commands in the terminal from the hr_app directory:

npm init -y
npm install express -s
node .

The npm init command creates the package.json file, which npm uses as a manifest file (the -y flag accepts the default options). The npm install command is used to install Express (the -s flag adds Express to the list of dependencies in package.json). npm stores the modules you install in the node_modules directory and also creates a file named package.lock.json to ensure that module trees are identical across a team of developers.

Do you see a message telling you that the web server is listening on localhost:3000? Congratulations, you’ve created an Express-based web server! Open Firefox, and navigate to http://localhost:3000, as shown in Figure 1.

opensource figure 1

Figure 1: Accessing the web server and the application

There it is, yet another “Hello World!” Although not particularly exciting, it’s an important first step for your API. When you’re ready, you can shut down the server by returning to the terminal and pressing Ctrl + c.

Controlling the Shutdown

Although shutting down by pressing Ctrl + c works, you didn’t have much control over how it happened. To control the shutdown process, you will need to explicitly close the web server and exit the Node.js process.

Append the following code to the bottom of the web server module.

// *** previous code above this line ***

function close() {
  return new Promise((resolve, reject) => {
    httpServer.close((err) => {
      if (err) {
        reject(err);
        return;
      }

      resolve();
    });
  });
}

module.exports.close = close;

The close function returns a promise that is resolved when the web server is successfully closed. The httpServer.close method stops new connections from being established, but it will not force already opened connections closed. Depending on how many connections are open and what they are doing, you might have to wait a bit for the callback to fire. Although you will not do it in this application, it is possible to use custom code or npm modules, such as http-shutdown, to force open connections to close.

With the close function in place, the main module can be updated to invoke it at the right times. Append the following code to the end of index.js:

// *** previous code above this line ***

async function shutdown(e) {
  let err = e;
   
  console.log('Shutting down');

  try {
    console.log('Closing web server module');

    await webServer.close();
  } catch (e) {
    console.log('Encountered error', e);

    err = err || e;
  }

  console.log('Exiting process');

  if (err) {
    process.exit(1); // Non-zero failure code
  } else {
    process.exit(0);
  }
}

process.on('SIGTERM', () => {
  console.log('Received SIGTERM');

  shutdown();
});

process.on('SIGINT', () => {
  console.log('Received SIGINT');

  shutdown();
});

process.on('uncaughtException', err => {
  console.log('Uncaught exception');
  console.error(err);

  shutdown(err);
});

SIGINT and SIGTERM events are related to signals that can be sent to the process to shut it down, such as when Ctrl + c is pressed. The uncaught exception event will occur when JavaScript code throws an error but doesn’t handle it.

Try running and shutting down the application again. You’ll know that everything is working correctly when you see the “shutting down” messages in the terminal.

Adding Web Server Logging

There’s just one last thing to round out your web server module: HTTP logging. There are various modules you could use for this type of logging, but morgan is known to be one of the best (and simplest). Let’s install morgan with npm.

npm install morgan -s

Next, add the following line to services/web-server.js under the line that requires Express (line 2).

const morgan = require('morgan');

Now you can incorporate morgan as a middleware function that all requests will go through with app.use. Add this line before the app.get call that produces the “Hello World!” message.

// Combines logging info from request and response
app.use(morgan('combined'));

// *** app.get call below this line ***

Note that app.use is creating a pipeline of middleware functions that can interact with HTTP requests and responses. The middleware functions will execute in the order in which they are included.

Restart the app, and position the terminal so you can see it and Firefox at the same time. Each time you reload the page in Firefox, you should see a new log entry appear in the terminal. By default, morgan streams log info to STDOUT (which is displayed in the terminal).

This is as far as I’ll go with logging in this series. However, in a production application, you might want to stream morgan’s output to an HTTP access log file. As an alternative to the console object, you might also consider a logging utility such as Winston for debugging and tracing.

The next article in this series focuses on database basics—including connection pooling—that will help you understand and build REST APIs for Node.js.

Next Steps

LEARN more about JavaScript and Oracle.

TRY Oracle Cloud.

GET more about this article’s code from GitHub.

Photography by Helloquence, Unsplash