Contents


Getting started with IBM Cloud Node.js applications, Part 1

Create a front-desk visitor log with Node.js

Developing the server-side code

Comments

Content series:

This content is part # of # in the series: Getting started with IBM Cloud Node.js applications, Part 1

Stay tuned for additional content in this series.

This content is part of the series:Getting started with IBM Cloud Node.js applications, Part 1

Stay tuned for additional content in this series.

In this article, you learn how to use the IBM Cloud to write a Node.js application for an organization’s front desk, which needs to log visitors in and out. Along the way you will learn how to use Node.js, the Express HTTP server library, and a Cloudant database. You will learn how to do this in the highly available IBM Cloud. This is a basic introduction to Node.js programming on the IBM Cloud platform.

What you'll need to build your application

  • A free IBM Cloud account.
  • Basic knowledge of HTML.
  • Basic knowledge of JavaScript.

Run the appGet the code

Getting started

First, we create the application. Then we create the development environment.

Getting started

First, we create the application. Then we create the development environment.

Create the application

  1. Log on to the IBM Cloud Console. If you do not have an IBM account, create one here.
  2. Expand the hamburger icon and select Cloud Foundry Apps.
  3. Click Create resource.
  4. Select the category Cloud Foundry Apps. Within that category, click SDK for Node.js.
  5. Click SDK for Node.js.
  6. Enter these parameters:
    App namePick an unused value
    Host nameAccept the default
    Domainmybluemix.net
    Region/locationChoose whichever is closest to you
    Organization and spaceLeave the default
  7. Click Create.
  8. Click Overview on the left sidebar.
  9. Click Visit App URL.
  10. You may have to reload until the application is created and started.
  11. Figure 1 shows the initial application. After you see it, do not close the tab. You will need it later.

Note: This would be your initial application. If you go to the sample application's URL you'll get a different one, a user interface we build later in the article.

Figure 1. Initial application
initial application
initial application

Create the development environment

Cloud Foundry on the IBM Cloud allows several development environments. In this article, we take the easy road and use the web development environment.

  1. Return to the IBM Console page for the application. If you are logged out, log back in and click the application's name in the dashboard.
  2. Scroll down to continuous delivery and click Enable.
  3. To accept the defaults click Create.
  4. After the tool chain is created, click Eclipse Orion Web IDE.
  5. Click Create new launch configuration > +.
  6. Click Save to create the default launch configuration. It has everything we need.
  7. Click Visitor-Log > app.js.
  8. Replace the content of the file with the code here.
  9. Click the play icon to apply the changes.
  10. Wait until you see the green circle again. It means that the application was restarted with the new code.
  11. Go back to the application tab and add /hello at the end of the URL. See that you get a Hello, world message.

To understand this program, we go line by line over the source code, available here.

The first line is ignored by the JavaScript interpreter as a comment. It is here because the IBM Console editor includes the Eslint utility to identify potential errors in the code. This directive tells that utility to treat the JavaScript as Node.js (server-side JavaScript) code. You can read more about the Eslint utility here.

/*Eslint-env node*/

This is the other way to write a comment in JavaScript. The rest of the line after a double slash (//) is ignored as a comment.

//------------------------------------------------------------------------------
// node.js starter application for Bluemix
//------------------------------------------------------------------------------

Node.js is the runtime environment. The web server package is Express. You can read more about it here. The way you use a package in Node.js is to use the require function with the package name (assuming the package is declared in the package.json file). The require function usually returns a function that you use to use the module.

// This application uses express as its web server
// for more info, see: http://expressjs.com
var express = require('express');

The cfenv module provides Cloudy Foundry information. It is used later to get the port number to which the web server needs to listen, and the URL it is serving.

// cfenv provides access to your Cloud Foundry environment
// for more info, see: https://www.npmjs.com/package/cfenv
var cfenv = require('cfenv');

The next step is to create an HTTP server.

// create a new express server
var app = express();

This is how we tell Express to serve static files from a directory. If you open the public directory in the editor, you will see the index.html file that is part of the default application (the one shown in Figure 1).

// serve the files out of ./public as our main files
app.use(express.static(__dirname + '/public'));

The app.get call specifies how to handle requests for a particular path. The path is the first parameter. The second parameter is a function to call when the path is requested. This function has two parameters: a request object that includes the HTTP request, and a response object used to send back a response. JavaScript has two syntax options for a function literal (an anonymous function which can, for example, be itself a function parameter). The older one, which we use here, is function(<parameters>) {body}.

The callback function just uses the res.send function to send a constant string, the traditional hello world.

The /* @callback */ comment is for Eslint. Normally, Eslint would report that a function has an unused parameter and suggest you remove it. However, the parameters of callback functions are set by the calling code (here, Express). In this case, some path callbacks require both parameters.

app.get("/hello", /* @callback */ function(req, res) {
	res.send("Hello, world");
});

This step gets the Cloud Foundry environment. This is how the IBM Cloud infrastructure communicates with the application. You can read more about this here.

// get the app environment from Cloud Foundry
var appEnv = cfenv.getAppEnv();

// start server on the specified port and binding host
app.listen(appEnv.port, '0.0.0.0', function() {
  // print a message when the server starts listening
  console.log("server starting on " + appEnv.url);
});

The visitor log

The main data structure of the application is a visitor log. You can see the source code with the code that declares and manipulates it here. Replace your current app.js and click the play button to restart the application. You can see the current visitor log in https://<your app>/test/visitors. It is in JSON, so it might be clearer if you use a JSON formatter such as this one. To see the visitor log for my application instance, click here.

The visitor log is an associative array (also called hash table, or object) whose keys are visitor names. The value for each visitor name is itself an associative array, which can have two attributes:

  • arrived – The last time the visitor arrived at the office. This attribute does not exist if the visitor is not currently in the office.
  • history – The visitor's history, an array of associate arrays with the start and end times of previous visits. This attribute does not exist during the first visit to the office.

The default visitor log

There is a default visitor log with some sample data for debugging. It is shown in Figure 2.

Figure 2. Default visitor log
idefaultvisitorlog
idefaultvisitorlog

You can see the definition for this default visitor log on GitHub, lines 23-48. Most of it is a standard JavaScript object definition, but the dates, instead of being fixed, are a fixed time before the application was started. Date.now() is the number of milliseconds between now and a fixed point in time (midnight January 1st 1970 GMT). So Bill Hamm's arrival time is two hours before this code is executed when the application starts. You can read more about the Date object here.

	"Bill Hamm": {
		arrived: new Date(Date.now() - 1000*3600*2) 
	},

Reading the visitor log

When displaying the visitor log, we might want to display all of it, or we might want to display just the current visitors, those that are currently in the office. Also, it can be useful to get the names of all the visitors, all the visitors currently in the office, or all those who currently aren't, without their full information. These are the functions that implement these requirements:

To get the list of visitor names, all we need is to get the keys of the visitors object. The function to do this is Object.keys. Note that the function syntax here is the new one, (parameters) => {expressions;}. In this particular case, there are no parameters.

var visitorNames = () => {
	return Object.keys(visitors);	
};

To get a subset (either those who are in the office or those who aren't), use the filter function. This function is an array method (so you can call it on any list, such as the one returned by visitorNames). It gets one parameter, a function. This function gets an item from the list as a parameter, and returns whether or not that item should be a member of the filtered list.

The function definition is simplified. When a function literal is a single expression, there is no need to enclose it in curly brackets ({}) and have a return statement. You can just enter the expression.

var currentVisitorNames = () => {
	return visitorNames().filter((name) => visitors[name].arrived !== undefined);
};

It is more complicated to get the list of current visitors with their information, with the names as keys. The first step is in currentVisitorList. This function uses the map function to retrieve the information for each user in the list of current visitors. This function receives a function as a parameter, similar to filter. However, instead of deciding whether a value will be in the output or not the function in map is used to transform the value. Those transformed values are turned into a list in the same order as the original values. You read about this function here.

In this case, for each name we get a structure with the user information. For example, for the default data we might get something like Figure 3.

var currentVisitorNames = () => {
	return visitorNames().filter((name) => visitors[name].arrived !== undefined);
};
Figure 3. Current visitor list
currentvisitorlist
currentvisitorlist

This list has the information we need, but each user is in a separate object in a list. It makes a lot more sense to use the same format we use in the visitors variable, one object with each user being a separate attribute. To do that, we use reduce to turn the list into a single object. If the list it receives has one item, it returns that item. If it has two items, it runs the parameter function on them and returns the result. If there are more, it runs the parameter function on two of them, then on that result and another one, until there is only a single value left. You can read more about the function here.

var currentVisitors = () => {
	return currentVisitorList().reduce((a, b) => {

To create the combined object, we first need the key of the new object, the one from the list which only has a single attribute for a single user.

		var bKey = Object.keys(b)[0];

We add this value to the original object, the one in which we accumulate all the users, and return it.

		a[bKey] = b[bKey];
		
		return a;
	});
};

Modifying the visitor log

There are two possible modifications to the visitor log. A user may be logged in when they enter the office, or logged out when they leave.

For these functions we have a get function and a set function to interact with the visitors variable. The purpose of these functions is to make it easy to modify the application to use a different data storage, for example a database, in the future.

var getVisitor = (name) => visitors[name];
var setVisitor = (name, values) => visitors[name] = values;

The log out function is relatively simple. Check whether the user has an arrived attribute. If the user doesn't have it, this user is not currently in the office and cannot be logged out. Report an error. If the arrived attribute does exist, remove it and add the visit that just ended to the history.

var logOut = (name) => {
	var oldRecord = getVisitor(name);

When a string is enclosed in back ticks (`), it means that it is a template literal. It can run for several lines, and you can use the syntax ${<expression>} to instead the value of an expression, for example the name variable. This is the error we return if there is no user at all:

	if (oldRecord === undefined)
		return `Error, ${name} is unknown`;

And this is the error we return if the user exists, but is not logged in at the moment.

	if (oldRecord.arrived === undefined)
		return `Error, ${name} is not logged in`;

The only attribute in the old record that is still relevant is history. We need to keep it and add a new value. However, if this is the first visit there might be no history yet. In such a case, the history is empty.

var history = oldRecord.history;

	// If this is the first visit
	if (history === undefined) 
		history = [];

The unshift function adds a new value at the beginning of an array. This is the behavior we want here, because the latest visits are more likely to be relevant, so it is best if the array is sorted in reverse chronological order. You can read more about this function here.

	history.unshift({
		arrived: oldRecord.arrived,
		left: new Date(Date.now())
	});

The only attribute for a visitor who is not currently logged on is history.

	setVisitor(name, {history: history});
	
	return `OK, ${name} is logged out now`;
};

The log in function creates a new record with the existing history and an arrived attribute with the current time.

var logIn = (name) => {
	var oldRecord = getVisitor(name);
	var history;

When you have multiple conditions, and you only want the first one that is true to be processed, you can use this syntax:

if <condition A> <action A> else if <condition B> <action B> else if <condition C> <action C> … else <default action>

Because of the else if statements, once a condition is evaluated as true the action for it happens and the rest of the conditions are skipped.

Here we need to check if there is an existing entry before we check if it has an arrived attribute because it is an error to check for attributes of an undefined value.

	// First time we see this person
	if (oldRecord === undefined)    
		history = [];   // No history
		
	// Already logged in	
	else if (oldRecord.arrived !== undefined) 
		return `Error, ${name} is already logged in`;
		

	// Not logged in, already exists
	else history = oldRecord.history;
		
	setVisitor(name, {
		arrived: new Date(Date.now()),
		history: history
	});	
	
	return `OK, ${name} is logged in now`;	
};

Testing

The code above looks like it should work, but it is best to test it before proceeding. For testing purposes, various paths under /test return the results of the above functions. Doing this relies on testFunctions, which is a table that has paths and their associated functions. The functions in this table do not receive any parameters, and return the information to send the user. To enable the testing of functions that do require parameters, such as logIn, they are wrapped in the table within a definition that does provide the necessary parameters. That way, the code that uses the table does not need to specify any parameter values.

var testFunctions = [

The first few lines are simply function names for functions that do not accept parameters. There is no need to wrap them.

	{path: "visitorNames", func: visitorNames},	
	{path: "currentVisitorNames", func: currentVisitorNames},	
	{path: "nonCurrentVisitorNames", func: nonCurrentVisitorNames},		
	{path: "currentVisitorList", func: currentVisitorList},		
	{path: "currentVisitors", func: currentVisitors},

The /test/visitors path does not actually show the output of a function, but a variable. However, the code that uses the table expects a function – so we wrap it in a function.

{path: "visitors", func: () => visitors},

The logIn and logOut functions do get a parameter, a user name. To run the simple tests we need here, we use a fixed user name.

	{path: "logIn", func: () => logIn("Avimelech ben-Gideon")},
	{path: "logOut", func: () => logOut("Avimelech ben-Gideon")}	
];

This is the code that registers the handlers. It prepends /test/ to the path and creates a function that calls the function from the table and then sends the response to the browser.

testFunctions.map((item) => 
	app.get(
		`/test/${item.path}`, 
		/* @callback */ function(req, res) {
			res.send(item.func());
		}
	)
);

To actually test the application, first go to /test/logOut to log out the non-existent Avimelech ben-Gideon account and see the error code. Then go to /test/logIn twice. The first attempt should succeed and the second one fails. Then go to /test/logOut twice to see the first attempt succeeds and the second one fails. After you do these steps, and maybe log in and out a few more times, go to /test/visitors to see the JSON data for the visitor log. The easiest way to do this is to go to a JSON formatter that accepts URLs and give it the visitor log URL. Finally, look at the other function URLs, such as /test/currentVisitorNames, to see that it shows the correct information.

Note that if you look at those paths on the sample application there might be excess information from other readers of this article.

User interface

Replace the code in your app.js with the file found here. Then go to /visitors on your application (or the sample application) to see the full visitor list. Go to /currentVisitors to see the current list. Log users in with /login and out with /logout. To see the entire user interface, go to / or /index.html.

The first difference is this instruction to the Eslint to ignore unused parameters. This is because the /* callback */ directive (ignore unused parameters only for this function call).

/*eslint-disable no-unused-params */

It is useful to know how long a visitor has been in the office, but a number such as 5000 seconds is hard to interpret. This function turns a time difference in milliseconds to a readable string. The input is milliseconds because that's the standard in most JavaScript functions that relate to time.

// Given a time difference in miliseconds, return a string 
// with the approximate value
var tdiffToString = (msec) => {
	var sec = msec/1000;

If the number of seconds is less than 60, return a number of seconds, properly labeled. Use the trinary operator to either add an "s" for plural or not. This operator takes three parameters. If the first parameter (less than 2 seconds) is true, it returns the second parameter (an empty string). If the first parameter is false, which means we need to use the plural, the operator returns the third parameter, the plural "s."

	if (sec < 60)
		return sec + " second" + (sec < 2 ? "" : "s");

If the number of seconds is less than 60, the function has already returned. If the number is the between 60 and 3600 (an hour), we need to return the number of minutes in the time interval. Note the use of Math.floor. It is necessary because we want to avoid reporting values like 5.23544 minutes.

	if (sec < 3600)
		return Math.floor(sec/60) + " minute" + (sec < 60*2 ? "" : "s");

The rest of the function, for hours and days, operates the same way.

…
};

This is the first function that produces HTML. The user interface displays the history (arrivals, departures, and the intervening time) in an HTML table – and this function produces the rows in that table.

// Given a history entry (arrived and left times), create a table row with 
// that information
var histEntryToRow = (entry) => {
	return `<tr>
		<td>${entry.arrived}</td>
		<td>${entry.left}</td>
		<td>${tdiffToString(entry.left-entry.arrived)}</td>
		</tr>`;
};

This function produces an entire table (if there is anything to produce, otherwise it just returns an empty string).

// Given a history, create a table with it
var histToTable = (history) => {
	if (history === undefined)
		return "";
		
	if (history.length === 0)
		return "";

People don't always want to see the history. By enclosing it in a details tag we hide the history until a user decides to open it. The style attribute allows us to specify the background color to make the history tables distinct from the rest of the visitor information (which is also displayed in table form).

	return `<details>
		<table border style="background-color: yellow">
			<tr>
				<th>
					Arrived
				</th>
				<th>
					Left
				</th>
				<th>
					Time here
				</th>
			</tr>
			${history.map(histEntryToRow).reduce((a, b) => a+b)}
		</table>
	</details>`;
};

The next two functions, userToRow and usersToTable, are very similar to the way we create the history table. There is no point to go over them. Next, visitorsHTML and currentVisitorsHTML add a heading and create the table with the appropriate user names list. Again, those functions are so similar that it is enough to see one of them.

var visitorsHTML = () => {
	return `
				<h2>Current Visitor List</h2>
					${usersToTable(visitorNames())}
		`;
};

This function creates a login form. It uses an HTML form with the GET method. This means the parameters (in this case there is only one, user) are going to be encoded in the URL. The login attribute gives the path for the handler, which you will see later in the article.

var loginForm = () => {
	return `<h2>Log in a visitor</h2>
			<form method="get" action="login">
				Visitor to log in: <input type="text" name="user">
			</form>`;
};

There is one substantial difference between the login form and the logout "form." There is no way to know in advance which visitor is going to be logged in. It may even be somebody completely new. However, the only visitors that can be logged out are those who are currently logged in. This lets us show a list and have the user choose from it.

var logoutForm = () => {
	if (currentVisitorNames().length === 0) 
		return "No users to log out";
	
	return `
		<h2>Log out a visitor</h2>
		<ul>
			${currentVisitorNames()

Instead of creating a form with an input field, I decided to use links. We can do that, and simulate a GET form, because of the the way a GET form returns its parameters. You have the usual URL (http://<hostname>/<path>) followed by a question mark, then <parameter>=<value> pairs, separated by ampersands (&). The value might include characters that have a special meaning in URLs (such the ampersand), so they are escaped. This is the role of the encodeURI function.

				.map(name => `<li> 
					<a href="logout?user=${encodeURI(name)}">${name}</a> 
					</li>`)
				.reduce((a,b) => a + b)}
		</ul>`;	
};

This function takes text, such as the HTML produced by the above functions, and turns it to a complete web page.

var embedInHTML = (str) => {
	return `<html><body>${str}</body></html>`;	
};

These two calls call the proper function to create the heading and table, embed it within a page, and respond.

app.get("/visitors", (req, res) => {
	res.send(embedInHTML(visitorsHTML()));
});

app.get("/currentVisitors", (req, res) => {
	res.send(embedInHTML(currentVisitorsHTML()));
});

These two calls are the form handlers. The parameters submitted to the form using the GET method are available in req.query. If there is no user, send the form. If there is a user, process the command for that user.

app.get("/login", (req, res) => {
	if (req.query.user === undefined)
		res.send(embedInHTML(loginForm()));
	else 
		res.send(logIn(req.query.user));
});
app.get("/logout", (req, res) => {
	if (req.query.user === undefined)
		res.send(embedInHTML(logoutForm()));
	else
		res.send(logOut(req.query.user));
});

This app.get call shows everything in the application. Instead of a path there is a list of paths, which Express interprets as "this handler applies to everything in this list."

app.get(["/index.html", "/"], (req, res) => {

app.get(["/index.html", "/"], (req, res) => {
	res.send(embedInHTML(`
		${loginForm()}
		<hr />
		${logoutForm()}
		<hr />
		${currentVisitorsHTML()}
		<hr />
		${visitorsHTML()}
	`));	
});

If we keep this call it overrides the handler we registered – so I commented it out.

/*
// serve the files out of ./public as our main files
app.use(express.static(__dirname + '/public'));
*/

Database

There is one "slight" problem with the application. Every time that it is restarted it loses the visitor log. To solve this, we store the visitor log in a Cloudant database. The visitor name is the key, and the data (history and arrival time and date) is the value.

Create the database

Follow these steps to create the database:

  1. In the IBM Cloud console, from the hamburger menu click Data & Analytics.
  2. Click Create resource and select Cloudant NoSQL DB.
  3. Name the service visitor-log and click Create.
  4. When the service is created, click Service credentials in the left sidebar.
  5. Click the New credential button.
  6. Name the new credential visitor-log and click Add.
  7. In the credential list, click View credentials. Click the copy/duplicate icon to copy the credential and paste it to a text file.
  8. In the left sidebar, click Manage. Then, click the LAUNCH button.
  9. In the left sidebar, click the Databases icon.
    dbicon
  10. Then click Create Database in the upper right.
  11. Name the database visitor-log.
  12. Go back to the application development environment and open the file package.json.
  13. In the dependencies, add a line for the Cloudant package (version 1.7.x). After you finish, your file will look like the one in the link.

Use the database

To use the database, replace app.js with this code. It uses almost the same data definitions and user interface, so it won't look different. But if you restart the application, the information is not lost.

First, have a variable with your Cloudant credential. Obviously, you can't use the value in GitHub because the credential is different for each user and I'm not disclosing mine.

var cloudantCred = {
<<
	
	redacted
	
>>
};

Use this credential to connect to the database management system, and then specify which database you are going to use:

// Connect to the database
var cloudant = require("cloudant")(cloudantCred.url);
var mydb = cloudant.db.use("visitor-log");

There are two ways we can store the information in the database. One would be to put the entire visitors data structure in a single database entry (called document in Cloudant). The other is to have a separate document for each visitor name. For this introductory article, I chose to use the first method. It is obviously less efficient, but it will work for up to 1 MB. The major advantage of this approach is that we can use the application logic developed previously with almost no change. We can continue to keep the visitors data structure as a variable.

The same Cloudant database could be used by multiple applications at the same time. To avoid one application's interference with another, it uses a revision system. Every document has a _rev value with the current revision. When you update a document, you provide the revision you're updating. If it changed from the time you read it, the revision is different causing the update to fail—this requires us to have a variable to hold the current revision.

var dbRev = "";

This function updates the visitor log on the database.

var updateVisitors = () => {

The data to update always includes _id, the key, and the actual value. It usually also includes _rev, the revision. The exception is when creating a new entry.

	var updateData = {
	    	"_id": "visitors",
        	value: visitors    	
	};
	
	if (dbRev !== "")
		updateData["_rev"] = dbRev;

With the update hash table ready, we can insert the latest version into the database, using the <database>.insert function.

	mydb.insert(updateData,
    	(err, body) => {

If the insertion is successful, the response body includes the new revision value.

    		if (err === undefined)
    			dbRev = body["rev"];

These lines log database failures. The mechanism used for this is explained in later in this article.

    		else
    			log += "updateVisitors: " + err + "
"; } ); };

The next few lines in the code read visitors when the application is started, using the <database>.get function.

// Read the visitors data structure. 
mydb.get("visitors", (err, body) => {

If we get a 404 error, it means there is no visitors data structure yet in the database. In this case, we create one and call updateVisitors. This is the one case where the database.insert function is called without a revision.

	// No visitors yet, create it.
    if (err !== null && err.statusCode === 404) {
    	visitors = {"Test user": {arrived: Date.now().valueOf()}};
    	updateVisitors();
	} else {

If there is a value, use it and keep the revision for when updateVisitors needs it.

		visitors = body.value;
		dbRev = body["_rev"];
	}

});

The only function that directly changes visitor information after the application starts is setVisitor. It needs to call updateVisitors to save the change in the database.

var setVisitor = (name, values) => {
	visitors[name] = values;
	updateVisitors();		
};

Time and date representation

There is one additional problem. Because the communication with Cloudant is text based, when writing to Cloudant Date objects are converted to their text representation, a string with the date, time, and time zone. Then, when the information is read back, they are treated as strings. This would be fine if we only wanted to display dates, but we also want the user to see how long each visit was.

To solve this problem, instead of storing a Date object in the visitors data structure and the database, we store the number of milliseconds since the beginning of the epoch (an arbitrary point in time, usually set on UNIX-derived systems to midnight on January 1, 1970, GMT). Storing milliseconds requires a few changes in the program:

First, every place where we get the current date needs to be Date.now().valueOf(). The valueOf() call turns the date object into the number of milliseconds since the beginning of the epoch. For example, in logout:

	history.unshift({
		arrived: oldRecord.arrived,
		left: Date.now().valueOf()
	});

The functions that display date and time stamps also need to change. To turn the time in milliseconds into a string, we create a new Date object with the time in milliseconds in one function, histEntryToRow:

// Given a history entry (arrived and left times), create a table row 
// with that information
var histEntryToRow = (entry) => {
	return `<tr>
		<td>${new Date(entry.arrived)}</td>
		<td>${new Date(entry.left)}</td>
		<td>${tdiffToString(entry.left-entry.arrived)}</td>
		</tr>`;
		
		// The Date need to be new, otherwise we are just 
// modifying the same object and all dates in
		// the history table are the same.
};

Logging for debug purposes

While developing this application, I needed to see why database updates did not work sometimes. To do that, I created a log string and appended to it every time an update failed. This technique can also be useful in your web applications (make sure to disable it or require a secret before you have production data).

need to be new, otherwise we are just 
var log = "";


var updateVisitors = () => {
	
...
		
	mydb.insert(updateData,
    	(err, body) => {
    		if (err === undefined)
    			dbRev = body["rev"];
    		else

When we add to the log, append a <br/>. This string will be retrieved by a browser, which will interpret it as HTML by default.

log += "updateVisitors: " + err + "
"; } ); };

To access the log, go to the /log path:

app.get("/log", (req, res) => {
	res.send(log);
});

Conclusion

You are now be able to write simple Node.js web applications on the IBM Cloud and store the data in Cloudant. The main problem with this web application is that it is not visually appealing. Every change requires the entire page to be refreshed, and it is plain HTML.

In the next article in this series, I show you how to use the Bootstrap Theme to make your web applications look better, and how to use the AngularJS library to make them more responsive.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Cloud computing, Security
ArticleID=1007137
ArticleTitle=Getting started with IBM Cloud Node.js applications, Part 1: Create a front-desk visitor log with Node.js
publish-date=04052018