- Engineering
- January 2016
Quick and Easy Tutorial: Learn Node.js in 90 Minutes
Last Updated: 21 January 2022
I am writing this blog to help beginners learn and start developing Node.js applications. This blog post is based on the meetup conducted by me on how anyone can start working and building basic Node.js applications in 90 minutes.
In this post you will learn and get familiar with the following things:
- What is Node.js?
- Why any body should go with Node.js?
- Installing Node.js
- Understanding NPM
- Bootstrap Node.js application
- Understanding & creating Modules
- Understanding Event Loop
- Using Express Framework
- Writing Unit Test in NodeJS
- Working with Session & Database
- Building sample API
What is Node.js?
Node is just JavaScript without a browser. Node wasn't just built from scratch, it came from another project. Node is actually based on the V8 JavaScript run time from Google Chrome. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
Node.js is having some more functions added to it and has removed some of the functions and objects that are available in Google Chrome.
Objects not available in Node.js are
- window
- document
- location
These objects are not available in Node.js because the Node.js application works on the server-side and does not have to deal with UI and URL handling. Instead of this object, Node.js has introduced a “global” object which holds the global stat across one Node.js process.
Objects not Available in chrome is
- global
Objects available and common in both
- console
Why any body should go with Node.js (Advantages of Node.js)?
Node.js is in contrast to today's more common concurrency model where OS threads are employed, uses event-driven programming. Thread-based networking is relatively inefficient and very difficult to use. Furthermore, users of Node are free from worries of dead-locking the process—there are no locks. Almost no function in Node directly performs I/O, so the process never blocks.
The way Node.js handles event-driven programming makes it helpful for handling things like streams and sockets and very large files.
As Node.js is non-blocking, which makes less-than-expert programmers are able to develop scalable applications.
Easy to adapt
Node.js makes it possible for you to take advantage of everything Javascript has to offer without being tied to a browser.
Hosting
With rapid adoption, world-class Node.js hosting is also proliferating. In particular, Platform-as-a-Service (PaaS) providers such as Modulus and others reduce deployments to a single command. Even the granddaddy of PaaS, Heroku, now formally supports Node deployments.
Easy to build Real-Time Application & Monitoring
You can easily build a real-time application using the most widely used “Socket.io” library in Node.js
Monitoring & Profiling of application is very using various Process manager available in Node.js
High-Performance
PayPal reported: double the number of requests per second and reduced response time by 35% or 200 milliseconds.
Walmart Labs had a bumper launch with Node.js in 2013, where they put all of their Mobile traffic through Node.js on black-friday, the busiest shopping period of the year.
The team at Walmart Labs live-tweeted against the #nodebf tag showing the performance of the Node.js application.
On Black Friday the Walmart servers didn’t go over 1% CPU utilization and the team did deploy with 200,000,000 users online.
Similarly, Groupon re-implemented their system in Node.js which resulted in page load times dropping by a whopping 50%.
The old story of LinkedIn was moving to Node.js from Rails for their mobile traffic, reducing the number of servers from 30 to 3 (90% reduction) and the new system was up to 20x faster.
The Future of Node.js is bright
It is backed by a huge community and sponsored by big corporate Joyent
Node.js’s package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
Eran Hammer announced at NodeDay that Walmart is planning to use Node.js across all eCommerce by the end of 2014.
PayPal is rolling out Node.js across their entire web application and Node.js will be used almost everywhere within 2 years.
Although Google has not publicly come out talking about their use of Node.js, there is evidence that they are using it, a simple search on LinkedIn shows almost 200 people in google with Node.js on their profile. According to an article on VentureBeat: “Googlers are definitely working on Node.js projects – perhaps even something big that will be released to the public in the near future. They’re just not ready to talk publicly about what that might be.”
Yahoo is a long way down the line with Node.js and has 200 developers doing Node.js full time with 800 public modules and 500 private ones.
New Entrants to Node.js such as Netflix and others show that there is big growth happening in this area at present.
Installing Node.js
You can install Node.js in the following different ways.
- Using pre-compiled binary
- Using Installers
- By compiling source
- Using NVM (Node Version Manager)
Let's see one by one each style of installing Node.js
Installing via pre-compiled binary & installer is very simple, you have to just go to the Node.js download page and get the node binary or Installer (.exe or .dmg) file and follow the instruction that the installer gives you.
Compiling Source
Python 2.6 or 2.7 is required to build from source tarballs.
Follow the steps below to install the Node.js by compiling it
- Download the source from the Node.js site.
- Extract the source in the folder and go inside that folder
- Run following commands in sequence
- ./configure
- make
- sudo make install
Using NVM
NVM is Node Version Manager, you can need to install NVM using the following command first to install using curl
to install using curl
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.30.1/install.sh | bash
to install using wget
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.30.1/install.sh | bash
Add these lines to your ~/.bashrc, ~/.profile, or ~/.zshrc file to have it automatically sourced upon login:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm
Once you install NVM life becomes very easy for installing and maintaining different versions of Node.js
To download, compile, and install the latest v5.0.x release of node, do this:
nvm install 5.0
And then in any new shell just use the installed version:
nvm use 5.0
Or you can just run it:
nvm run 5.0 --version
Or, you can run any arbitrary command in a subshell with the desired version of the node:
nvm exec 4.2 node --version
You can also get the path to the executable to where it was installed:
nvm which 5.0
For more information on NVM and its usage visit the following link
https://github.com/creationix/nvm
Understanding NPM
Npm is the package manager for Node.js called Node Package Manager. It was created in 2009 as an open-source project to help JavaScript developers easily share packaged modules of code. The npm Registry is a public collection of packages of open-source code for Node.js, front-end web apps, mobile apps, robots, routers, and countless other needs of the JavaScript community. npm is the command-line client that allows developers to install and publish those packages.npm, Inc. is the company that hosts and maintains all of the above.
To install any node module & save it as a dependency without which application cannot run using the following command
npm install <module_name> --save
To install any node module & save it as a dependency as developer dependency which is required to run on a developer machine, use the following command
npm install <module_name> --save-dev
To uninstall the module, run the following command
npm uninstall <module_name> --save
To install all the modules in the developer machine which are listed in package.json file of the project, run the following command
npm install
To install all the modules in the production machine which are listed in package.json file of the project, run the following command
npm install --production
Bootstrapping Node.js Application
To start working with a fresh new application and bootstrap if you need to run the following command
cd <source-folder>
npm init
Once you run this command it will ask you following question, just provide proper info and continue, your application will be ready to go.
- name —It is the name of application, it should not contain blank space
- version — version of your application, default is (1.0.0)
- description — Full info about your application
- entry point — Its the file which is the main entry point of your application
- test command — The BASH command(s) to be executed when "npm test” command is executed
- git repository — Endpoint of your GIT repo of the project
- keywords — In case you are building NPM package this keywords will be used to index your application for search in the NPM repository
- author — Name of the author/person who is main contact person or module author.
- license — If any license is applicable in case you are creating NPM package.
Once you answer all the questions, it will prompt for confirmation, once you confirm “yes” a “package.json” file will be created in your folder with appropriate details and you are now ready to go.
Understanding & Creating Modules
Node.js has a simple module loading system. In Node.js, files and modules are in one-to-one correspondence. As an example, module-sample.js loads the module module1.js, module2.js, module3.js in the same directory and module4 which is in module4/index.js.
The contents of module-sample.js:
var module1=require('./module1.js');
module1();
var module2=require('./module2.js');
var prefix='Phd';
module2('Dipesh');
var module3=require('./module3.js');
module3.add(12,10);
module3.sub(12,10);
var module4=require('./module4');
module4.add(12,10);
module4.sub(12,10);
The contents of module1.js
'use strict';
module.exports=function(){
console.log('Hello World');
};
The contents of module2.js
var prefix='Mr';
module.exports=function(name){
console.log(`Hello ${prefix} ${name}`);
};
The contents of module3.js
'use strict';
function add(a,b){
console.log(`${a} + ${b} = ${a+b}`);
}
function sub(a,b){
console.log(`${a} - ${b} = ${a-b}`);
}
module.exports={
add:add,
sub:sub
};
The contents of module4/index.js
'use strict';
function add(a,b){
console.log(`${a} + ${b} = ${a+b}`);
}
function sub(a,b){
console.log(`${a} - ${b} = ${a-b}`);
}
module.exports={
add:add,
sub:sub
};
To add functions and objects to the root of your module, you can add them to the special exports object.
Variables local to the module will be private, as though the module was wrapped in a function. In this example, the variable prefix is private to module2.js
If you want the root of your module's export to be a function (such as a constructor) or if you want to export a complete object in one assignment instead of building it one property at a time, assign it to module.exports instead of exports, as shown in module4/index.js
The module system is implemented in the require(“module”) module.
To get the exact filename that will be loaded when require() is called, use the require.resolve() function, which loads the file based on the following algorithm.
require(X) from module at path Y
1. If X is a core module,
a. return the core module
b. STOP
2. If X begins with './' or '/' or '../'
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. THROW "not found"
LOAD_AS_FILE(X)
1. If X is a file, load X as JavaScript text. STOP
2. If X.js is a file, load X.js as JavaScript text. STOP
3. If X.json is a file, parse X.json to a JavaScript Object. STOP
4. If X.node is a file, load X.node as binary addon. STOP
LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
a. Parse X/package.json, and look for "main" field.
b. let M = X + (json main field)
c. LOAD_AS_FILE(M)
2. If X/index.js is a file, load X/index.js as JavaScript text. STOP
3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
4. If X/index.node is a file, load X/index.node as binary addon. STOP
LOAD_NODE_MODULES(X, START)
1. let DIRS=NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
a. LOAD_AS_FILE(DIR/X)
b. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I &amp;amp;amp;amp;amp;amp;gt;= 0,
a. if PARTS[I] = "node_modules" CONTINUE
c. DIR = path join(PARTS[0 .. I] + "node_modules")
b. DIRS = DIRS + DIR
c. let I = I - 1
5. return DIRS
Modules are cached after the first time they are loaded. This means (among other things) that every call to require(‘foo’) will get exactly the same object returned if it would resolve to the same file.
Multiple calls to require('foo') may not cause the module code to be executed multiple times. This is an important feature. With it, "partially done" objects can be returned, thus allowing transitive dependencies to be loaded even when they would cause cycles.
If you want to have a module execute code multiple times, then export a function, and call that function.
Modules are cached based on their resolved filename. Since modules may resolve to a different filename based on the location of the calling module (loading from node_modules folders), it is not a guarantee that require('foo') will always return the exact same object, if it would resolve to different files.
When there are circular require() calls, a module might not have finished executing when it is returned.
File Modules
If the exact filename is not found, then Node.js will attempt to load the required filename with the added extensions: .js, .json, and finally .node
- js files are interpreted as JavaScript text files
- json files are parsed as JSON text files
- node files are interpreted as compiled add on modules loaded with dlopen
A required module prefixed with ‘/' is an absolute path to the file. For example, require('/home/dpatel/foo.js’) will load the file at /home/dpatel/foo.js
A required module prefixed with ‘./' is relative to the file calling require(). That is, bar.js must be in the same directory as foo.js for require(‘./bar.js’) to find it.
Without a leading '/', './', or '../' to indicate a file, the module must either be a core module or is loaded from a node_modules folder.
If the given path does not exist, require() will throw an Error with its code property set to MODULE_NOT_FOUND
Folders as Modules
It is convenient to organize programs and libraries into self-contained directories, and then provide a single entry point to that library. There are three ways in which a folder may be passed to require() as an argument.
The first is to create a package.json file in the root of the folder, which specifies the main module. An example package.json file might look like this:
{
"name" : "some-library",
"main" : "./lib/some-library.js"
}
If this was in a folder at ./some-library, then require(‘./some-library') would attempt to load ./some-library/lib/some-library.js
This is the extent of Node.js's awareness of package.json files.
If there is no package.json file present in the directory, then Node.js will attempt to load an index.js or index.node file out of that directory. For example, if there was no package.json file in the above example, then require(‘./some-library') would attempt to load:
- /some-library/index.js
- /some-library/index.node
Understanding the Event Loop
Node's "event loop” enables the node to handle high throughput scenarios. It is the reason Node can essentially be "single-threaded" while still allowing an arbitrary number of operations to be handled in the background.
To understand how the event loop works, we need to understand the Event-Driven programming paradigm, event-driven programming is widely used in UI applications.
Event-driven programming is the application flow control that is determined by events or changes in state. Its implementation is to have a central mechanism that listens for an event and calls the callback function associated with it when an event has been detected. This is the basic principle behind the node’s event loop
In client side’s javascript development. If you see, button.onClick(), this is called when the user clicks on the button. Similarly, node handles the situation where the same element emits multiple events using EventEmitter, which you can find in Socket, HTTP, Server, etc modules.
Many objects in Node.js emit events, All objects which emit events are instances of events.EventEmitter. You can access this module by doing: require("events");
Typically, event names are represented by a camel-cased string, however, there aren't any strict restrictions on that, as any valid property key will be accepted.
Functions can then be attached to objects, to be executed when an event is emitted. These functions are called listeners. Inside a listener function, this refers to the EventEmitterthat the listener was attached to.
There is one common misconception about Event Emitter is that it seems to be asynchronous in nature on its own, but this is not correct.
Let me show you the example to show this
var EventEmitter=require('events').EventEmitter;
var util=require('util');
function SampleEmitter() {
EventEmitter.call(this);
}
util.inherits(SampleEmitter, EventEmitter);
var me = new SampleEmitter();
SampleEmitter.prototype.doCall = function doCall() {
console.log('before');
me.emit('fire');
console.log('after');
};
me.on('fire', function() {
console.log('emit fired');
});
me.doCall();
var fs = require("fs");
fs.readFile('package.json', function (err, data) {
if (err){
console.log(err.stack);
return;
}
console.log(data.toString());
});
console.log("Program Ended");
EventEmitter often appears asynchronous because it is regularly used to signal the completion of asynchronous operations, but the EventEmitter API is entirely synchronous. The emit function may be called asynchronously, but note that all the listener functions will be executed synchronously, in the order, they were added, before any execution can continue in statements following the call to emit.
In an event-driven application, there is generally a main loop that listens for events and then triggers a callback function when one of those events is detected.
For more information visit the blog: Event Loop with Javascript
Using Express Framework
Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. With a myriad of HTTP utility methods and middleware at your disposal, creating a robust API is quick and easy. Express provides a thin layer of fundamental web application features, without obscuring Node.js features that you know and love.
You can see express API in more details at link http://expressjs.com/en/4x/api.html
Installing Express framework
Assuming you’ve already installed Node.js, create a directory to hold your application and make that your working directory.
$ mkdir myapp
$ cd myapp
Use the npm init command to create a package.json file for your application. For more information on how package.json works, see Specifics of npm’s package.json handling.
$ npm init
This command prompts you for a number of things, such as the name and version of your application. For now, you can simply hit RETURN to accept the defaults for most of them, with the following exception:
entry point: (index.js)
Enter app.js, or whatever you want the name of the main file to be. If you want it to be index.js, hit RETURN to accept the suggested default file name.
Now install Express in the app directory and save it in the dependencies list. For example:
$ npm install express --save
To install Express temporarily and not add it to the dependencies list, omit the --save option:
$ npm install express
Creating Hello World Example
First create a directory named myapp, change to it, and run npm init. Then install express as a dependency, as per the installation steps above.
In the myapp directory, create a file named app.js and add the following code:
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
The app starts a server and listens on port 3000 for connections. The app responds with “Hello World!” for requests to the root URL (/) or route. For every other path, it will respond with a 404 Not Found.
Run the app with the following command:
$ node app.js
Then, load http://localhost:3000/ in a browser to see the output.
Basic Routing in Express
Routing refers to determining how an application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (GET, POST, and so on).
Each route can have one or more handler functions, which are executed when the route is matched.
Route definition takes the following structure:
app.METHOD(PATH, HANDLER)
Where:
- the app is an instance of express.
- METHOD is an HTTP request method.
- PATH is a path on the server.
- HANDLER is the function executed when the route is matched.
Respond with Hello World! on the homepage:
app.get('/', function (req, res) {
res.send('Hello World!');
});
Respond to POST request on the root route (/), the application’s home page:
app.post('/', function (req, res) {
res.send('Got a POST request');
});
Respond to a PUT request to the /user route:
app.put('/user', function (req, res) {
res.send('Got a PUT request at /user');
});
Respond to a DELETE request to the /user route:
app.delete('/user', function (req, res) {
res.send('Got a DELETE request at /user');
});
For more details on Routing visit: http://expressjs.com/en/guide/routing.html
Middleware in Express
Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.
Middleware functions can perform the following tasks:
- Execute any code.
- Make changes to the request and the response objects.
- End the request-response cycle.
- Call the next middleware in the stack.
If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.
Here is a simple example of a middleware function called “sampleMiddleware”. This function just prints “Middleware Called” when a request to the app passes through it. The middleware function is assigned to a variable named sampleMiddleware.
var sampleMiddleware = function (req, res, next) {
console.log(‘Middleware Called');
next();
};
To load the middleware function, call app.use(), specifying the middleware function. For example, the following code loads the sampleMiddleware middleware function before the route to the root path (/).
var express = require('express');
var app = express();
var sampleMiddleware = function (req, res, next) {
console.log(‘Middleware Called');
next();
};
app.use(sampleMiddleware);
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(3000);
Every time the app receives a request, it prints the message “Middleware Called” to the terminal.
The order of middleware loading is important: middleware functions that are loaded first are also executed first.
If sampleMiddleware is loaded after the route to the root path, the request never reaches it and the app doesn’t print “Middleware Called”, because the route handler of the root path terminates the request-response cycle.
The middleware function sampleMiddleware simply prints a message, then passes on the request to the next middleware function in the stack by calling the next() function.
An Express application can use the following types of middleware:
- Application-level middleware
- Router-level middleware
- Error-handling middleware
- Built-in middleware
- Third-party middleware
You can load application-level and router-level middleware with an optional mount path. You can also load a series of middleware functions together, which creates a sub-stack of the middleware system at a mount point.
You can find more information about middleware at http://expressjs.com/en/guide/using-middleware.html
Working with Database
Here in this blog, we will see how to connect with MongoDB and use mongoose.
I assume here that you are already aware of what is MongoDB and has installed MongoDB, if you haven’t installed MongoDB go to the following link to install it on Linux https://docs.mongodb.org/manual/administration/install-on-linux/
Mongoose provides a straightforward, schema-based solution to model your application data. It includes built-in type casting, validation, query building, business logic hooks, and more, out of the box.
Installing Mongoose
Assuming you’ve already installed Node.js, create a directory to hold your application and make that your working directory.
Now install Mongoose in the app directory and save it in the dependencies list. For example:
$ npm install mongoose --save
To install Mongoose temporarily and not add it to the dependencies list, omit the --save option:
$ npm install mongoose
Now lets see how to connect to MongoDB and interact with Mongoose.
Creating the connection with mongoDB
var mongoose=require('mongoose');
mongoose.connect('mongodb://localhost/blogs');
Defining the model definition for a collection that holds blog details
//Blog Model definition
var Blog=mongoose.model('Blog',{
title:'String',
content:'String',
author:'String'
});
Finding all blogs
// Find all blogs
Blog.find({},function(err,blogs){
if(err){
res.status(500);
res.send('');
} else{
res.status(200);
res.send(blogs);
}
});
Adding New Blog to collection
// Add New Blog
var blog=new Blog();
blog.title=req.body.title;
blog.content=req.body.content;
blog.author=req.body.author;
blog.save(function(err,blog){
if(err){
//handle error
}else{
//blog will have object for the newly created blog
}
});
Finding single blog based on id
Blog.findOne({_id:1},function(err,blog){
if(err){
//When blog with id is not found
}else{
// blog found
}
});
Deleting blog based on id
Blog.remove({_id:1},function(err,result){
if(err){
//When blog with id is not found
}else{
// blog removed
}
});
NOTE: We will be seeing things kept together in Sample API
Working with Session
We will see an express-session module for working with sessions in Express. “express-session” is a simple module that provides session tracking functionality in the Express framework.
Installing express-session
Assuming you’ve already installed Node.js, create a directory to hold your application and make that your working directory.
Now install express-session in the app directory and save it in the dependencies list. For example:
$ npm install express-session --save
To install express-session temporarily and not add it to the dependencies list, omit the --save option:
$ npm install express-session
Now that you have installed express-session, let's just go ahead and start using it. A simple example using express-session to store page views for a user is given below, in this example, we are creating a sample express application that stores the views count based on the URL called and severed by it.
In below code we have created session object by calling var session = require('express-session')
Then we have injected and used the session as express middleware with app.use(session….
with adding it as middle ware express req.session will get populated in the request parameter of the routes.
Then we have create one more middleware(countMiddleware) that counts the visit based on URL and stores it in req.session object.
var express = require('express')
var parseurl = require('parseurl')
var session = require('express-session')
var app = express()
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
var countMiddleware=function (req, res, next) {
var views = req.session.views
if (!views) {
views = req.session.views = {}
}
// get the url pathname
var pathname = parseurl(req).pathname
// count the views
views[pathname] = (views[pathname] || 0) + 1
next()
}
app.use(countMiddleware)
app.get('/foo', function (req, res, next) {
res.send('you viewed this page ' + req.session.views['/foo'] + ' times')
})
app.get('/bar', function (req, res, next) {
res.send('you viewed this page ' + req.session.views['/bar'] + ' times')
});
You can find other options and more useful information about the express-session middleware at https://github.com/expressjs/session
In this example, sessions are stored in the process memory so if you restart your app, all the sessions will get destroyed, if you need to make your session persistent you need to use Session Store compatible to “express-session”. We will see in more detail in our Sample API which is the next section, about storing sessions in MongoDB.
Below is the list of well known Session Store compatible with “express-session"
- cassandra-store An Apache Cassandra-based session store.
- cluster-store A wrapper for using in-process / embedded stores - such as SQLite (via knex), leveldb, files, or memory - with node cluster (desirable for Raspberry Pi 2 and other multi-core embedded devices).
- connect-couchbase A couchbase-based session store.
- connect-mssql A SQL Server-based session store.
- connect-monetdb A MonetDB-based session store.
- connect-mongo A MongoDB-based session store.
- connect-mongodb-session Lightweight MongoDB-based session store built and maintained by MongoDB.
- connect-pg-simple A PostgreSQL-based session store.
- connect-redis A Redis-based session store.
- connect-session-knex A session store using Knex.js, which is a SQL query builder for PostgreSQL, MySQL, MariaDB, SQLite3, and Oracle.
- connect-session-sequelize A session store using Sequelize.js, which is a Node.js / io.js ORM for PostgreSQL, MySQL, SQLite and MSSQL.
- connect-sqlite3 A SQLite3 session store modeled after the TJ's connect-redis store.
- express-nedb-session A NeDB-based session store.
- level-session-store A LevelDB-based session store.
- mssql-session-store A SQL Server-based session store.
- nedb-session-store An alternate NeDB-based (either in-memory or file-persisted) session store.
- session-file-store A file system-based session store.
- session-rethinkdb A RethinkDB-based session store.
Building Sample API
Here I am going to show you code snippets that get a RESTFul API for creating a blog, along with a dummy login check and session management. The basic goal of this sample api is to cover all the fundamentals we have seen above and implement them.
In the sample code below we have the following main file to focus on
- blog-app.js — This file act as a module that can be later used for unit testing, it contains the express application that does add session middleware handle mongo connection and expose “app” object of the express application. It also contains endpoints that allow user to log in and log out.
- blog.js — This file contains the router and API paths that allow to the following functionality, it also contains one middleware that checks for the session and rejects the request with 401 status code if the session is not valid.
- Add Blog (POST /blogs)
- Update Blog (PUT /blogs/:blogId)
- Delete Blog (DELETE /blogs/:blogId)
- Get Blog Details (GET /blogs/:blogId)
- blog-server.js — This file makes use of the “blog-app” module and starts the server @ port 3000.
- package.json — This file contains metadata about the application and dependency list.
So if you put these files together in a folder and execute the following command then the application will start running on port 3000, and you will be able to see it in the browser at http://localhost:3000
$ npm install
$ node blog-server.js
First command in above snippet will install all the dependencies and second command will start the server.
Make sure you have MongoDB running in your machine.
Code of blog-app.js
var express=require('express');
var app=express();
var mongoose=require('mongoose');
var bodyParser=require('body-parser');
var session=require('express-session');
var MongoStore=require('connect-mongo')(session);
//Init mongoose connection
mongoose.connect('mongodb://localhost/blogs');
var Blog=mongoose.model('Blog',{
title:'String',
content:'String',
author:'String'
});
global.Blog=Blog;
//Add Body parser
app.use(bodyParser.json({type:'application/json'}));
app.use(session({
secret:'tiest9383D829d',
resave:true,
saveUninitialized:true,
store:new MongoStore({mongooseConnection:mongoose.connection})
}));
var blogController=require('./blog');
app.use('/blogs',blogController);
app.post('/login',function(req,res){
if(req.body.username=='dipesh'){
req.session.isLoggedIn = 'Y';
req.session.user = 'Dipesh';
res.send('Success');
} else{
res.status(500);
res.end();
}
});
app.get('/logout', function(req,res){
req.session.destroy();
res.status(205);
res.end();
});
module.exports=app;
Code for blog.js
'use strict';
var express=require('express');
var router=express.Router();
router.use(function(req,res,next){
if(req.session.isLoggedIn=='Y'){
next();
}else{
res.status(401);
res.end();
}
});
router.get('/',function(req,res){
Blog.find({},function(err,blogs){
if(err){
res.status(500);
res.send('');
}else{
res.status(200);
res.send(blogs);
}
});
});
router.post('/',function(req,res){
var blog=new Blog();
blog.title=req.body.title;
blog.content=req.body.content;
blog.author=req.body.author;
blog.save(function(err,blog){
if(err){
res.status(500);
res.send('');
}else{
res.status(201);
res.send(blog);
}
})
});
router.get('/:blogId',function(req,res){
Blog.findOne({_id:req.params.blogId},function(err,blog){
if(err){
res.status(404);
res.send('');
}else{
res.status(200);
res.send(blog);
}
});
});
router.put('/:blogId',function(req,res){
Blog.findOne({_id:req.params.blogId},function(err,blog){
if(err){
res.status(404);
res.send('');
}else{
blog.title=req.body.title;
blog.content=req.body.content;
blog.author=req.body.author;
blog.save(function(err){
if(err){
res.status(500);
res.send('');
}else{
res.status(204);
res.send('');
}
})
}
});
});
router.delete('/:blogId',function(req,res){
Blog.findOne({_id:req.params.blogId},function(err,blog){
if(err){
res.status(404);
res.send('');
} else{
blog.remove(function(err){
if(err){
res.status(500);
res.send('');
}else{
res.status(204);
res.send('');
}
});
}
});
});
module.exports=router;
Code for blog-server.js
'use strict';
var app=require('./blog-app');
var server=app.listen(3000,function(err){
if(err){
console.log('Error starting blog server');
console.log(err);
} else{
console.log(`Blog Server running on port ${server.address().port}`);
}
});
Code for package.json
{
"name": "sample-node-app",
"version": "1.0.0",
"description": "Sample Node Application",
"main": "server.js",
"scripts": {
"test": "mocha blog-test.js"
},
"author": "Dipesh Patel",
"license": "MIT",
"dependencies": {
"body-parser": "^1.14.2",
"connect-mongo": "^1.1.0",
"express": "^4.13.3",
"express-session": "^1.12.1",
"mongoose": "^4.3.4"
},
"devDependencies": {
"mocha": "^2.3.4",
"should": "^8.0.2",
"supertest": "^1.1.0",
"supertest-session": "^2.0.0"
}
}
Unit Testing Code
Now here we will see how to Unit test one of our APIs from the above sample.
To do unit testing we need to follow things
- Assertion library
- Test Framework
- Test Runner
- Express API executer, without a running server
So for that, we are going to use the following NPM packages
- should — assertion library (https://github.com/tj/should.js/)
- mocha — test framework and runner (https://github.com/mochajs/mocha)
- supertest — a Express API executer (https://github.com/visionmedia/supertest)
- supertest-session — a extension to support cookie support for superset. (https://www.npmjs.com/package/supertest-session)
Below file named “blog-test.js” is the file you need to put along with the files in above sample api.
Code for blog-test.js
'use strict';
var app=require('./blog-app');
var supertest=require('supertest-session');
var should=require('should');
describe('Testing Blog APIs',function(){
it('Get blog details api should return error 401 if login does not exists',function(done){
supertest(app)
.get('/blogs/1')
.expect(401)
.end(function(err,response){
response.status.should.be.equal(401);
done();
});
});
it('Get Blog Details api should return blog details if blog exists',function(done){
var req=supertest(app);
req
.post('/login')
.set('Content-Type','application/json')
.send({username:'dipesh'})
.expect(200)
.end(function(err,response){
response.status.should.be.equal(200);
req
.get('/blogs/568f845316099856f1cdfdf9')
.expect(200)
.end(function(err,response){
response.status.should.be.equal(200);
response.body.title.should.not.be.empty();
done();
});
});
});
it('Get Blog details api should return error 404 if blog does not exists',function(done){
var req=supertest(app);
req
.post('/login')
.set('Content-Type','application/json')
.send({username:'dipesh'})
.expect(200)
.end(function(err,response){
response.status.should.be.equal(200);
req
.get('/blogs/1')
.expect(404)
.end(function(err,response){
response.status.should.be.equal(404);
done();
});
});
})
});
The above file contains the test case for testing Blog APIs,
First, in the above code, we include the libraries that are required for testing, which are as below, here we created the instance of the ‘blog-app” module, superset-session module, and should module
var app=require('./blog-app');
var supertest=require('supertest-session');
var should=require('should');
The below statement is used to tell mocha that it contains the definition for the TestSuite, describe is used to define the test suites for the application, you can have more than one test suite in one test file also.
describe('Testing Blog APIs',function(){});
Each test case in the test suite is represented using the “it” function, as below. Here we are creating one test case that checks for the status of the response and does the assertion on it. The first argument in the “it” function is the name of the test case and the second argument is the function having one parameter “done” which will be called when a test case is completed executing. You can pass error object as the first parameter of a done function call to notify that test case has failed, and if you have written “throw” or any assertion is getting failed then that test case will be marked as failed at that point only and next test case in sequence will be executed.
it('Get blog details api should return error 401 if login does not exists',function(done){
supertest(app)
.get('/blogs/1')
.expect(401)
.end(function(err,response){
response.status.should.be.equal(401);
done();
});
});
Here in the above code, we called supertest with the app object passed to it as a parameter, then we call the API endpoint to call using the “get” statement on line 3 in the above code, then we say super test to expect the return status as “401” and finally, we end the request so the request will be fired and response or error will be given in callback function.
In the callback function, we are checking the status of the response to be 401, if that fails our test case will fail, else the test case will pass.
To run the above test you need to run the following command from your terminal inside your application folder
$ mocha blog-test.js
this command will start the test case execution and give the following output in case of all test cases get passed.
Testing Blog APIs
✓ Get blog details api should return error 401 if login does not exist (44ms)
✓ Get Blog Details api should return blog details if the blog exists (45ms)
✓ Get Blog details api should return error 404 if the blog does not exist
3 passing (112ms)