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.
- Install Node.js development.
- Install MongoDB. Follow the steps and get the mongodb service running by configuring it.
- Get familiar with express.
- What is multer?
- Real-time engine using Socket.io.
- Q - Promises and related stuff.
- 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.
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.
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
Install mime by typing the following command.
npm install mime --save
Install body-parser now
npm install body-parser --save
Install multer using the following command.
npm install multer --save
Install Socket.io like below.
npm install socket.io --save
Install MongoDB now. An npm package for providing the APIs required to manipulate the MongoDB data.
npm install mongodb --save
Next is installing Q for the promises and deferred objects we use mainly during the MongoDB data manipulations.
npm install q --save
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
Now we will install the type-definitions for express.
tsd install express --save
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.
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.
Create an empty NodeJS project by opening new projects from Visual Studio 2015. I use Community Edition as shown in the linked article.
Clicking the "Ok" in the "New Project" dialog will start a wizard and follow the pictures below to complete the project creation.
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.
Final step is below which provides the name of the project file. I am accepting the default.
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.
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.
Now copy the following code to the server.ts file created now.
"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;
app.use(bodyParser.urlencoded({ extended: false }));
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.
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.
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.
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.
Here is the source code to be pasted in the dal.ts file.
var MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var Q = require("q");
export class DataAccess {
static shareItUrl: string = 'mongodb://127.0.0.1:27017/ShareIt';
dbConnection: any = null;
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;
});
}
}
public closeDbConnection() {
if (this.dbConnection) {
this.dbConnection.close();
this.dbConnection = null;
}
}
public getStudentsCount(): any {
return this.getDocumentCount('Students');
}
public insertStudent(student: any): any {
return this.insertDocument(student, 'Students');
}
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;
}
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;
}
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.
The code for the base.ts file is below.
import express = require('express');
import da = require('./dal');
export class baseController {
dataAccess: da.DataAccess;
socket: any;
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);
}
}
public setSocket = (socket: any) => {
this.socket = socket;
}
}
We will add the StudentController code next.
The code is below.
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;
}
}
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());
}
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);
}
}
}
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.
"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;
app.use(bodyParser.urlencoded({ extended: false }));
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);
});
});
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.
Here are some client interactions.
Another one.
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.