Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / ExtJS

Node.js Application written in Typescript with MongoDB, Express and Socket.io.

4.11/5 (6 votes)
28 Mar 2016CPOL6 min read 50.8K   330  
Step by step development of a Node.js application in Typescript using Express and MongoDB with real-time update using Socket.io

Introduction

This article is a continuation of my previous article which has the server implemented using Microsoft ASP.Net and SQLServer.

Background

The code that was used in the above article for illustration was part of a real application and it happened that hosting the server on IIS was not feasible based on some of the constraints existed.

To overcome that problem, I was writng the server using Node.js. In addition to that, any changes in the resources added needed to be updated real-time to the clients connected at any point of time. This was resolved using Socket.IO. Instead of storing the resources and user information in SQLServer, MongoDB was introduced and it was easy to use based on the JSON type data received and to be transmitted to the client.

I am using some of that server code for illustration here. The AngularJS/Typescript client code from that article can still be used as a client to communicate to the Node.JS server in this article with minor changes.

Server Development

Before going to the basics of development, I will list some steps required to make the environment ready for developing this application.

  1. Install Node.js development.
  2. Install MongoDB. Follow the steps and get the mongodb service running by configuring it.
  3. Get familiar with express.
  4. What is multer?
  5. Real-time engine using Socket.io.
  6. Q - Promises and related stuff.
  7. Get latest Typescript. I am using this one for Visual Studio (My compilation is in VS) - version 1.8.6

The development of this server gets complex as it progresses and they are listed below in a sequence.

Basic

Create a new folder where the server code is to be placed. In my case, it is "C:\Temp\NodeJS-ShareIt".

Open the Node.js command prompt and change the folder to the new folder created before.

Image 1

 

We are using Node package manager to initialize the project. Type "npm init" and answer the wizard style questions to generate a package.json file.

Image 2

The folder has a new package.json file created now. You are free to open this file and edit the values if you want. Be careful about the beginning and ending quotes of the keys and values in the JSON.

Next we will start installing the individual packages for this project.

Install express by typing the following command.

npm install express --save

Image 3

Install mime by typing the following command.

npm install mime --save

Install body-parser now

npm install body-parser --save

Image 4

Install multer using the following command.

npm install multer --save

Image 5

Install Socket.io like below.

npm install socket.io --save

Image 6

Install MongoDB now. An npm package for providing the APIs required to manipulate the MongoDB data.

npm install mongodb --save

Image 7

Next is installing Q for the promises and deferred objects we use mainly during the MongoDB data manipulations.

npm install q --save

Image 8

Next is installing the type-definitions for node.js and express. Before doing that we need to install "tsd" globally. You may notice that tsd is deprecated. My goal was just to get the type-definitions, which still works using tsd. So using tsd for now.

npm install tsd -g

Image 9

Now we will install the type-definitions for express.

tsd install express --save

Image 10

Following picture shows the content of the package.json file. The test command is there as part of the project but no details are provided in this article.

Image 11

Next we are adding the source code required for this Node.js application. To make things easier, we are using Visual Studio and the Node.js tools for Visual Studio installed separately.

Create an empty folder "scripts" so that we can add new files there.Image 12

Create an empty NodeJS project by opening new projects from Visual Studio 2015. I use Community Edition as shown in the linked article.

Image 13

Clicking the "Ok" in the "New Project" dialog will start a wizard and follow the pictures below to complete the project creation.

Image 14

Click "Next" to open the next wizard window. Don't specify the startup file for now. We can do it later after adding the files.

Image 15

Final step is below which provides the name of the project file. I am accepting the default.

Image 16

Clicking finish will create a new Node.js project with the typings and the two ".json" files included.

Right click on the "scripts" folder to add a new item.

Image 17

You can see that "node_modules" folder is not included and it is not necessary.

Name the new file as "server.ts". When we did the "npm init", we specified the same name.

Image 18

Now copy the following code to the server.ts file created now.

JavaScript
"uses strict"
var http = require('http');
import express = require('express');
var fs = require('fs');
var bodyParser = require('body-parser');

var app = express();
app.use(express.static("../ShareIt-Client"));
var server = http.Server(app);
var io = require('socket.io')(server);
var port = process.env.port || 8080;

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));

// parse application/json
app.use(bodyParser.json());

app.get('/', (req, res) => {
    fs.readFile('index.html',
        (err, data) => {
            if (err) {
                res.writeHead(500);
                return res.end('Error loading index.html');
            }

            res.writeHead(200);
            res.end(data);
        });
});

io.on('connection', (socket) => {
    console.log('a user connected');
    socket.on('disconnect', function () {
        console.log('user disconnected');
    });
});

server.listen(port, _ => {
    console.log('listening on *: ' + port);
});

Build the project. If the right Typescript version is installed for the Visual Studio, the project can be built without any issues.

We can debug the source code from Visual Studio. To do this we need to specify the startup file. See the picture below.

Image 19

The Node.js port is not set and because of that a default port is assigned by Visual Studio.

Alternately, the server can be started by the command as shown below. In this case, the client need to be opened in a browser.

Image 20

Another way to debug is using the "node-debug" from the Node.js command prompt. This is pretty much the same as using the F12 Developer tools. See below for how this will show the debugging environment.

Image 21

Download NodeJS-ShareIt Server code - 31 KB

The client code resides in a folder "ShareIt-Client" which is in the same level as the C:\Temp\NodeJS-ShareIt. The source code for the client is pasted below.

Download ShareIt-Client Client code - 1.6 MB

Advanced

The steps and code above was given to get a working Node.js server and a client to connect to the server.

There is more code listed below to make the server capable of logging in a user and registering a new user.

Add a new typescript file "dal.ts" in the scripts folder. The code in this file is responsible for connecting to the MongoDB, disconnecting from it, inserting new document to a named collection and retrieving documents.

Image 22

Here is the source code to be pasted in the dal.ts file.

JavaScript
var MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var Q = require("q");

// Create a class to manage the data manipulation.
export class DataAccess {
    static shareItUrl: string = 'mongodb://127.0.0.1:27017/ShareIt';
    dbConnection: any = null;

    // Open the MongoDB connection.
    public openDbConnection() {
        if (this.dbConnection == null) {
            MongoClient.connect(DataAccess.shareItUrl, (err, db) => {
                assert.equal(null, err);
                console.log("Connected correctly to MongoDB server.");
                this.dbConnection = db;
            });
        }
    }

    // Close the existing connection.
    public closeDbConnection() {
        if (this.dbConnection) {
            this.dbConnection.close();
            this.dbConnection = null;
        }
    }

    // Get the current count of Student entities.
    public getStudentsCount(): any {
        return this.getDocumentCount('Students');
    }

    // Insert a new Student.
    public insertStudent(student: any): any {
        return this.insertDocument(student, 'Students');
    }

    // Get a new Student based on the user name.
    public getStudent(userName: string): any {
        var deferred = Q.defer();
        if (this.dbConnection) {
            var cursor = this.dbConnection.collection('Students').find();
            cursor.each((err, document) => {
                assert.equal(err, null);
                if (err) {
                    deferred.reject(new Error(JSON.stringify(err)));
                }
                else if (document !== null && document['userName'] === userName) {
                    return deferred.resolve(document);
                }
                else if (document === null) {
                    return deferred.resolve(document);
                }
            });
        }

        return deferred.promise;
    }

    // Insert a new document in the collection.
    private insertDocument(document: any, collectionName: string): any {
        var deferred = Q.defer();
        this.dbConnection.collection(collectionName).insertOne(document, (err, result) => {
            assert.equal(err, null);
            if (err) {
                deferred.reject(new Error(JSON.stringify(err)));
            }
            deferred.resolve(result);
        });

        return deferred.promise;
    }

    // Get the count of all documents in the collection.
    private getDocumentCount(collectionName: string): any {
        var deferred = Q.defer();
        this.dbConnection && this.dbConnection.collection(collectionName).count((err, result) => {
            assert.equal(err, null);
            if (err) {
                deferred.reject(new Error(JSON.stringify(err)));
            }
            deferred.resolve(result);
        });
        return deferred.promise;
    }
}

The mongod service is running on my machine and it is configured to listen in the port 27017. The first time when the code is run, a ShareIt database is created. Follow the steps in the MongoDB documentation to get it working before running the code.

Next a new file is created which contains the class baseController to act as the base class for all the controllers. In this article, we are adding only the StudentController and manipulating only the student data.

Image 23

The code for the base.ts file is below.

JavaScript
import express = require('express');
import da = require('./dal');

// Create a class to act as the base class for all the controllers.
export class baseController {
    dataAccess: da.DataAccess;
    socket: any;

    // Send an error message.
    sendErrorMessage = (res: express.Response, e?: Error) => {
        if (e) {
            var ex = JSON.stringify(e);
            return res.status(400).json({ Message: e.message, ExceptionMessage: ex });
        }
        else {
            res.sendStatus(400);
        }
    }

    // Set the socket.
    public setSocket = (socket: any) => {
        this.socket = socket;
    }
}

We will add the StudentController code next.

Image 24

The code is below.

JavaScript
import express = require('express');
import da = require('./dal');
import base = require('./base');
var Q = require("q");

export interface IStudent {
    id: number;
    firstName: string;
    lastName: string;
    userName: string;
    password: string;
}

class Student implements IStudent {
    public id: number;
    public firstName: string;
    public lastName: string;
    public userName: string;
    public password: string;

    constructor(student: IStudent) {
        this.id = student.id;
        this.firstName = student.firstName;
        this.lastName = student.lastName;
        this.userName = student.userName;
        this.password = student.password;
    }
}

// Create a class to manage the Students entity.
export class StudentController extends base.baseController {
    constructor(app: express.Express, da: da.DataAccess) {
        super();
        this.dataAccess = da;
        app.get("/api/Students", this.getStudent());
        app.post("/api/Students", this.postStudent());
    }

    // Register Student
    postStudent = (): any => {
        var da = this.dataAccess;
        var em = this.sendErrorMessage;
        return (req: express.Request, res: express.Response) => {
            var student = new Student(<IStudent>req.body);
            if (student != null) {
                da.getStudent(student.userName).then((result) => {
                    if (result) {
                        return em(res, { name: "Error", message: "A user with same username exist" });
                    }

                    da.getStudentsCount().then((count) => {
                        student.id = count + 1;
                        da.insertStudent(student).then((reult) => {
                            res.sendStatus(201);
                        }).catch((e) => {
                            return em(res, e);
                        });
                    }).catch((e) => {
                        return em(res, e);
                    });
                }).catch(e => {
                    return em(res, e);
                });
            }
            else {
                em(res);
            }
        }
    }

    // Get a Student.
    getStudent = (): any => {
        var da = this.dataAccess;
        var em = this.sendErrorMessage;
        return (req: express.Request, res: express.Response) => {
            da.getStudent(req.query.userName).then((result) => {
                if (result) {
                    res.json(result);
                }
                else {
                    return em(res, { name: "Error", message: "User not found" });
                }
            }).catch(e => {
                return em(res, e);
            });
        }
    }

}

We need to modify the server.ts file that is added in Basic step above. The controllers have to be created and the DataAccess connections need to be managed.

Here is the full code for the server.ts.

JavaScript
"uses strict"
var http = require('http');
import express = require('express');
var fs = require('fs');
var bodyParser = require('body-parser');

import st = require('./student');
import da = require('./dal');

var app = express();
app.use(express.static("../ShareIt-Client"));
var server = http.Server(app);
var io = require('socket.io')(server);
var port = process.env.port || 8080;

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));

// parse application/json
app.use(bodyParser.json());

app.get('/', (req, res) => {
    fs.readFile('index.html',
        (err, data) => {
            if (err) {
                res.writeHead(500);
                return res.end('Error loading index.html');
            }

            res.writeHead(200);
            res.end(data);
        });
});

// Create Student controller
var dataAccess = new da.DataAccess()
var studentController = new st.StudentController(app, dataAccess);

io.on('connection', (socket) => {
    console.log('a user connected');
    dataAccess.openDbConnection();

    socket.on('disconnect', function () {
        dataAccess.closeDbConnection();
        console.log('user disconnected');
    });
});

server.listen(port, _ => {
    console.log('listening on *: ' + port);
});

Here are some screenshots of the server in action. First one is a Get call using Postman.

Image 25

Here are some client interactions.

Image 26

Another one.

Image 27

Points of Interest

There were many occassions that I got stuck and it is not a surprise for anyone who tries new technologies and approaches.

The original code was developed sometime back and while creating the server, I had issues related with typescript. The old typescript compiler (1.7) was not able to compile the new node.js typedef file. I updated the compiler to 1.8.6 and the issue was resolved.

MongoDB need to be running as a service and listening in the right port. If you have set it to listen elsewhere, please update the code.

I will update more code and issues later to this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)