Beginners Guide to Building Real-World Microservices with Node.js

Cloud 66
8 min readSep 12, 2018

--

This is a guest post by Mike Mackrory, a Senior Engineer on a Quality Engineering team and a technical writer at Sweetcode.

This article is intended to be a quick-start guide for developing real-world microservices with Node.js. It’s the article that I would have liked to read when I started programming with Node.js. Therefore, you won’t find the words “Hello” or “World” anywhere in this article (at least, not more than that first mention). You should have a basic understanding of Javascript, but other than that, follow the steps for a working application.

In our highly connected world, projects can be constructed by combining functionality from different APIs in unique and performant ways. In this example, we’re going to create a microservice using Node.js which connects to an external API, and then look at improvements we can make to make it more cost-effective and performant.

The business requirement for this service is to accept two Zip Codes and return the distance between them in miles. We’ll look at validation techniques, the configuration for an external API call, and implement an internal cache to make duplicate calls faster and more cost-efficient.

Initial Steps

You’ll need to have Node.js installed on your workstation for this example. I’m using version 8.11, and you can download the latest version for your operating system from Nodejs.org. The Node.js installation includes NPM, which is the Node.js package manager. We’ll use NPM to bootstrap the project, install dependencies and execute the service. (You can follow along with this example or view the completed example in this GitHub repository.)

We’ll start by initializing the project using npm init. Run this command in the root folder for the project. The command walks you through the creation of the package.json file, which is the foundation for the project. If you’re not sure of an answer, accept the default, and we can update it later if necessary. We’ll be using the name and version a little later.

$ npm init

We’re going to use a package called Express to build our service. Express is an established framework for Node applications and continues to benefit from the support of the Node.js Foundation. We’re also going to use the Request package to enable us to connect with a third-party API on the Web.

We’ll add these packages to our project with the following command (the save flag at the end of the command adds it to the package.json file which was created by NPM).

$ npm install express request --save

One last thing before we delve into the code. Let’s look at the structure of how it’s all going to fit together. There are two files and a folder created by the npm init command. These are package.json, package-lock.json, and node_modules. When we installed the express and request packages, their dependencies were downloaded and saved in node_modules.

The primary file in our project is named server.js. We’ll have files to support the API in a folder called api, and we’ll add the logic to connect to a third-party API in the service folder.

Let’s build our service!

Creating a Server to Accept Requests

We’ll start from our primary file, then define the routes, add controllers, and finally, build the connection to an external service.

Create a file in the root folder for your project called server.js. This file contains the code below. I’ll step through each part of it, so you can see how it goes together.

var express = require('express') var app = express(); var port = process.env.PORT || 3000; var routes = require('./api/routes'); routes(app); app.listen(port, function() {      console.log('Server started on port: ' + port); });

This file creates our server and assigns routes to process all requests. We’re going to have two endpoints which we’ll define in the next step.

The first thing we’ll do is to bring the express package into the file. We’ll use this to create a new app object. We’ll also specify the port. The process object is used to reference system properties. If I want to define the port number based on the machine running the application, I can define an environment variable called PORT, and it will be used. The default port value is 3000 if it isn’t defined.

The next line brings in a routes object from the routes.js file in the api folder. We’ll pass the app to this object, and that sets the routes for our application. Finally, we’ll tell the app to start listening on the port we defined, and to display a message to the console when this process is complete.

Defining the Routes

The next step is to define the routes for the server and assign each to a target in our controller object. We’ll build the controller in the next step. We’ll have two endpoints. An about endpoint returns information about the application. A distance endpoint includes two path parameters, both Zip Codes. This endpoint returns the distance, in miles, between these two Zip Codes.

'use strict'; var controller = require('./controller'); 
module.exports = function(app) {
app.route('/about') .get(controller.about); app.route('/distance/:zipcode1/:zipcode2') .get(controller.get_distance); };

The ‘use strict’ directive at the top of the file is used by new versions of Javascript to enforce secure coding practices. (You can learn more about that here.) The first thing we’ll do is to create a controller object which we’ll define in the next step. Because we declare this outside of any functions in the file, it is accessible by all of them.

Module.exports allows us to declare functions inside a module, and have them available for use in another file. This file constitutes the routes module, which we imported into our primary server.js file and used it to define the routes for our express app.

This function adds two routes to the app. The first route listens for GET requests on the /about endpoint. These requests are handled by the about function in the controller. The second route listens for GET requests on the /distance endpoint. The get_distance function in the controller handles these requests. Two parameters are also specified. These are labeled zipcode1 and zipcode2 respectively.

Let’s build the controller to handle those requests.

Adding Controller Logic

One of the aspects of Javascript and Node.js which I find interesting is that functions are objects. Within the controller file, we’re going to create a controller object with two properties. Those properties are the functions to handle the requests we defined in the routes module.

'use strict'; var properties = require('../package.json') var distance = require('../service/distance'); 
var controllers = {
about: function(req, res) { var aboutInfo = { name: properties.name, version: properties.version } res.json(aboutInfo); }, get_distance: function(req, res) { distance.find(req, res, function(err, dist) { if (err) res.send(err); res.json(dist); }); }, };
module.exports = controllers;

We have two distinct parts to our controller. We’ll go through the code for the about functionality first. At the top, we create an object called properties which references the package.json file which NPM created when it bootstrapped the project. This file is in JavaScript Object Notation or JSON for short. This format affords us the ability to import and use the information it contains.

Within the controllers object, we define a property called about. This property is a function which accepts request and response objects. We’ll only use the response object for this function. Using the name and version information from the package.json file, we’ll build a new object and return it as the response.

For the get_distance functionality, we’ll start by bringing in the distance module. We’ll pass the request and response objects to the find function within this module. This function also includes a callback function. This function accepts an error object (err) and a distance object (dist). If there is an error in the response, we return that with our response; otherwise, we send back the results of the find function.

Making the External Call

We’re ready for the final piece of the puzzle. This file handles the call to a third-party API. We’ll use the distance API provided by ZipCodeAPI.com. (You need an API key to use this, and it is free if you register. You can also use the key from the example if you want to test your service, but this key frequently expires during the day.)

I set my key as an environment variable on my system and named it ZIPCODE_API_KEY. The default key in the code is an expired test key from the ZipCodeAPI website.

var request = require('request'); 
const apiKey = process.env.ZIPCODE_API_KEY ||
"hkCt1nW1wF1rppaEmoor7T9G4ta7R5wFSu8l1dokNz8y53gGZHDneWWVosbEYirC"; const zipCodeURL = 'https://www.zipcodeapi.com/rest/'; var distance = { find: function(req, res, next) { request(zipCodeURL + apiKey + '/distance.json/' + req.params.zipcode1 + '/' + req.params.zipcode2 + '/mile', function (error, response, body) { if (!error && response.statusCode == 200) { response = JSON.parse(body); res.send(response); } else { console.log(response.statusCode + response.body); res.send({distance: -1}); } }); } }; module.exports = distance;

We’re using the request package to execute the external HTTP request, and we already discussed the api Key above. Ensure that you update it unless you want to start by testing the error conditions.

The find function accepts request, response and next objects as parameters. The request object accepts the URL of the service we’re calling and then defines a callback function to handle the response.

If there are no errors, and the status of the response is HTTP Status code 200, then the function parses out the body of the response into an object called response and returns it on the resp object. Since the ZipCodeAPI returns with a JSON response, we could forward this directly. Parsing it out allows us the option of doing more with the response if we choose to.

We log failures to the console, and then a result of -1 is sent on the response object. You may opt to create an error object to return as well.

Finally, we export the distance object, which allows the controller to instantiate it and call its functions as needed.

Execution and Debugging

Assuming there aren’t any typos, your application should be ready to execute. Open a console window and run the following commands — The first ensures that all the packages used by the application are downloaded and up-to-date:

$ npm install

The second command starts your application.

$ npm start

Assuming it starts correctly, and the port you define is 3000, you can now open your browser and navigate to:

http://localhost:3000/about

http://localhost:3000/distance/84010/97229

The following URL allows you to see the error condition because 92001 is not a recognized Zip Code.

http://localhost:3000/distance/84010/92001

If you run into problems, I’ve found the error messages to generally be helpful in pointing me to the line of code with the offending syntax, missing punctuation, or other problems. The Internet is also a wealth of information for specific problems.

Related Content:

Originally published at blog.cloud66.com on September 12, 2018.

--

--

Cloud 66

DevOps-as-a-Service to help developers build, deploy and maintain apps on any Cloud. Sign-up for a free trial by visting: www.cloud66.com