Skip to content

Latest commit

 

History

History
468 lines (318 loc) · 19.1 KB

File metadata and controls

468 lines (318 loc) · 19.1 KB
# Building the Backend#
## Overview ##

A Websocket is a protocol designed to allow web applications to create a full-duplex channel over TCP between the web browser and a web server. It is fully compatible with HTTP and uses TCP port number 80. WebSockets allow real-time web applications to support advanced interactions between the client and the server.

Socket.IO is a simple JavaScript library and Node.js module that allows you to create real-time bidirectional event-based communication apps simply and quickly. It simplifies the process of using WebSockets significantly.

Azure DocumentDB is a NoSQL document database service designed from the ground up to natively support JSON and JavaScript directly inside the database engine. It’s the right solution for applications that run in the cloud when predictable throughput, low latency, and flexible query are key.

This demo introduces the use of the Socket.IO module that allows for real-time bidirectional communication. Here we see how to connect, broadcast and receive messages in a chat app. It also shows how to use Azure DocumentDB, a NoSQL database, to save and retrieve messages.

### Goals ### In this demo, you will see how to:
  1. Create bidirectional communication between client and server by using Socket.IO module

  2. Add Azure DocumentDB to your app to retrieve and save messages

### Key Technologies ### ### Setup and Configuration ### Follow these steps to set up your environment for the demo.
  1. Download Visual Studio Code for your platform and follow the installation and setting up instructions.

  2. Install Node.js.

  3. Open File Explorer and browse to the source/Setup folder.

  4. Copy the nodecamp-buildingthebackend-snippets folder and paste it into the Visual Studio Code Extensions folder to install the JavaScript snippets for this demo. Depending on your platform it is located:

  • Windows: %USERPROFILE%\.vscode\extensions
  • Mac: $HOME/.vscode/extensions
  • Linux: $HOME/.vscode/extensions
  1. Open a command prompt/terminal according to your platform in the source/Begin/Chatroom folder.

  2. Run npm install to install all the missing dependencies.

    Installing Missing npm Packages

    Installing Missing npm Packages

### Using the Code Snippets ###

Throughout the demo document, you will be instructed to insert code blocks. For your convenience, most of this code is provided as code snippets, which you can access from within Visual Studio Code to avoid having to add it manually.

Note: This demo is accompanied by a starting solution located in the Begin folder that allows you to follow the demo. Inside the source code you will also find an End folder containing the code that results from completing the steps in the demo. You can use this folder as guidance if you need additional help as you work through this demo.


## Demo ## This demo is composed of the following segments:
  1. Creating a chat server with Socket.IO
  2. Saving messages to an Azure DocumentDB database
  3. Creating a DocumentDB database account
  4. Configuring local environment variables
  5. Appendix: Saving messages to a MongoDB database
### Creating a chat server with Socket.IO ###
  1. In the command prompt/terminal run npm install --save socket.io to install the socket.io package.

    Note: This will install socket.io to your project and add it to the package.json file.

    Installing Socket.IO package

    Installing Socket.IO package

  2. Run code . to open the current directory with Visual Studio Code.

  3. In the Explore view, open the package.json file located in the root of the folder.

    Showing the package.json file

    Showing the package.json file

  4. In the Explore view, create a new socketio.js file by clicking the New File icon and fill its content with the following code snippet.

    Speaking point: We will log each user entering the chatroom by hooking a callback function to be executed on every single "connection" event via WebSocket to our HTTP server.

    (Code Snippet - BuildingTheBackend-SocketioOnConnection)

    module.exports = function (io, config) {
    	 io.on('connection', function (socket) {
    		  console.log('a user connected');
    	 });
    };

    Creating a new socketio.js file

    Creating a new socketio.js file

  5. Add the following code snippet inside the callback of the connection event.

    Speaking point: To do the same for when a user leaves, we have to hook up to the “disconnect” event for each socket.

    (Code Snippet - BuildingTheBackend-SocketioOnDisconnect)

    socket.on('disconnect', function () {
    	console.log('user disconnected');
    });
  6. Add the following code snippet inside the callback of the connection event below the disconnect event handler.

    Speaking point: Socket.IO gives us a function called emit that we can use to send an event. We will use it to broadcast any message received on the "chat" channel to all the other connections on this socket.

    (Code Snippet - BuildingTheBackend-SocketioOnChat)

    socket.on('chat', function (msg) {
    	socket.broadcast.emit('chat', msg);
    });
  7. Open the www file located in the bin folder and insert the following code snippet at the end of the file.

    Speaking point: Here we capture the HTTP server created by express in a variable called server and pass it to the socket.io module so it can attach to it. We then pass the socket.io instance to the module we created.

    (Code Snippet - BuildingTheBackend-SocketioInitialization)

    var io = require('socket.io')(server);
    require('../socketio')(io);
  8. Run the application using Visual Studio Code debugger. To do this, you will first need to setup your debugging launch configuration file (launch.json). Bring up the Debug view by clicking on the Debugging icon in the View Bar on the side of Visual Studio Code. Click on the Configure gear icon and select Node.js as your Debug Environment; this will generate a launch.json. Make sure that the Lunch configuration is selected in the dropdown and press F5 to start debugging. For more infomation, see the Debugging documentation.

    Launching the application in Debug mode with Visual Studio Code

    Launching the application in Debug mode with Visual Studio Code

  9. Open two instances of your browser, navigate to http://localhost:3000 and show that messages are being broadcasted to all the connections on the socket.

    Testing socket.io

    Testing socket.io

  10. Switch back to Visual Studio Code and stop the debugger.

### Saving messages to an Azure DocumentDB database ###
  1. Switch back to the command prompt/terminal and run npm install --save documentdb to install the documentdb package.

    Installing documentdb package

    Installing documentdb package

  2. Go back to Visual Studio Code. In the Explore view, add a new JavaScript file named docDBUtils.js.

    Speaking Point: We will create a few helper functions in this new file.

  3. Add the following code snippet in the docDBUtils.js file you created.

    (Code Snippet - BuildingTheBackend-DocDBUtilsStructure)

    var DocDBUtils = {
    };
    
    module.exports = DocDBUtils;
  4. Add the following function inside the DocDBUtils object definition to get or create a database.

    Speaking Point: This helper function will help to create or get a database.

    (Code Snippet - BuildingTheBackend-DocDBUtilsGetOrCreateDatabase)

    getOrCreateDatabase: function (client, databaseId, callback) {
        var querySpec = {
            query: 'SELECT * FROM root r WHERE r.id=@id',
            parameters: [{
                    name: '@id',
                    value: databaseId
                }]
        };
        
        client.queryDatabases(querySpec).toArray(function (err, results) {
            if (err) {
                callback(err);
    
            } else {
                if (results.length === 0) {
                    var databaseSpec = {
                        id: databaseId
                    };
                    
                    client.createDatabase(databaseSpec, function (err, created) {
                        callback(null, created);
                    });
    
                } else {
                    callback(null, results[0]);
                }
            }
        });
    },
  5. Add the following function inside the DocDBUtils object definition to get or create a collection below the previous function.

    Speaking Point: This helper function will help to create or get a collection in a database.

    (Code Snippet - BuildingTheBackend-DocDBUtilsGetOrCreateCollection)

    getOrCreateCollection: function (client, databaseLink, collectionId, callback) {
        var querySpec = {
            query: 'SELECT * FROM root r WHERE r.id=@id',
            parameters: [{
                    name: '@id',
                    value: collectionId
                }]
        };
        
        client.queryCollections(databaseLink, querySpec).toArray(function (err, results) {
            if (err) {
                callback(err);
    
            } else {
                if (results.length === 0) {
                    var collectionSpec = {
                        id: collectionId
                    };
                    
                    var requestOptions = {
                        offerType: 'S1'
                    };
                    
                    client.createCollection(databaseLink, collectionSpec, requestOptions, function (err, created) {
                        callback(null, created);
                    });
    
                } else {
                    callback(null, results[0]);
                }
            }
        });
    },
  6. Then, add the following function at the end of the DocDBUtils object definition to simplify the initialization of a collection.

    Speaking Point: This helper function will help to create or get a collection in a database by just calling the ones defined previously.

    (Code Snippet - BuildingTheBackend-DocDBUtilsInitCollection)

    initCollection: function (client, databaseId, collectionId, callback) {
        var self = this;
        
        DocDBUtils.getOrCreateDatabase(client, databaseId, function (err, db) {
            if (err) {
                callback(err);
            } else {
                DocDBUtils.getOrCreateCollection(client, db._self, collectionId, callback);
            }
        });
    }
  7. Now, open the socketio.js file you created in the previous section and add the following code snippet at the beginning of the file.

    (Code Snippet - BuildingTheBackend-DocDBInitialization)

    var DocumentDBClient = require('documentdb').DocumentClient;
    var docdbUtils = require('./docDBUtils');
  8. Add the following code snippet below the console.log('a user connected');.

    Speaking point: We want to connect to the database using the URI we have in the DOCUMENT_DB_HOST environment variable using the key we have in the DOCUMENT_DB_AUTH_KEY environment variable, and insert the chat message received in the socket connection.

    (Code Snippet - BuildingTheBackend-DocDBClientInitialization)

    var docDbClient = new DocumentDBClient(process.env.DOCUMENT_DB_HOST, {
    	masterKey: process.env.DOCUMENT_DB_AUTH_KEY
    });
  9. Add the following code snippet below the previous snippet.

    Speaking Point: We want to emit the previously received chat messages on the same channel.

    (Code Snippet - BuildingTheBackend-DocDBLoadMessages)

    docdbUtils.initCollection(docDbClient, 'chatroom', 'messages', function (err, collection) {
    	if (err) {
    		console.warn(err.message);
    	} else {
    		docDbClient.queryDocuments(collection._self, 'SELECT r.content FROM root r')
    			.forEach(function (err, msg) {
    				if (msg) {
    					console.log('emitting chat');
    					socket.emit('chat', msg.content);
    				}
    			});
    	}
    });
  10. Add the following code snippet inside the chat event handler and before the socket.broadcast.emit function call.

    Speaking point: We want to insert the chat message received in the socket connection.

    (Code Snippet - BuildingTheBackend-DocDBSaveMessages)

    docdbUtils.initCollection(docDbClient, 'chatroom', 'messages', function (err, collection) {
    	 if (err) {
    		  console.warn(err.message);
    	 } else {
    		  var documentDefinition = { content: msg };
    		  docDbClient.createDocument(collection._self, documentDefinition, function (err, o) {
    				if (err) {
    					 console.warn(err.message);
    				} else {
    					 console.log("chat message inserted into db: " + msg);
    				}
    		  });
    	 }
    });
### Creating a DocumentDB database account ###
  1. Sign in to the online Microsoft Azure portal.

  2. In the Jumpbar, click New, then Data + Storage, and Azure DocumentDB.

    Creating a new DocumentDB account

    Creating a new DocumentDB account

  3. In the New DocumentDB account blade, specify the desired configuration for the DocumentDB account.

    Setting up the new DocumentDB account

    Setting up the new DocumentDB account

  4. Once the new DocumentDB account options are configured, click Create. It can take a few minutes for the DocumentDB account to be created. To check the status, you can monitor the progress on the Startboard or from the Notifications hub.

  5. After the DocumentDB account is created, it is ready to use with the default settings in the online portal. Now navigate to the Keys blade of your DocumentDB account; we will use these values in the web application we create next.

    Copying the DocumentDB account keys

    Copying the DocumentDB account keys

### Configuring local environment variables ###
  1. Switch back to Visual Studio Code and open the launch.json file located in the .vscode folder.

  2. Add the following variables inside the configurations/env node, replacing the values with those obtained in the previous section.

    "DOCUMENT_DB_HOST": "https://{your-document-db}.documents.azure.com:443/",
    "DOCUMENT_DB_AUTH_KEY": "{Your-DocumentDb-primary-key}"

    Setting up the Environment Variables

    Setting up the Environment Variables

  3. Run the application using the Visual Studio Code debugger.

  4. Open your browser, navigate to http://localhost:3000 and send a few messages.

  5. Restart the browser and navigate again to http://localhost:3000. Show how the messages you sent are retrieved from the DocumentDB account.

    Retrieving messages from DocumentDB

    Retrieving messages from DocumentDB

  6. Switch back to Visual Studio Code and stop the debugger.


### Appendix: Saving messages to a MongoDB database ###
  1. Install the MongoDB package as you did with the socket.IO package in previous segment.

  2. Add the following code before var app = express(); in the app.js file:

    var mongo = require('mongodb').MongoClient;

  3. Add the next code snippet inside the "connection" event and after console.log('a user connected');:

    Speaking point: We want to give our users the last 10 messages from the chatroom so they have some context when they have just joined. To do that, we need to connect to Mongo. We use the limit function to limit the results to only 10 messages. We will stream the results from Mongo so that we emit them as soon as they arrive to the chatroom.

    (Code Snippet - NodeJsSocketIO-mongoDB-retrieve-messages)

    mongo.connect(process.env.MONGODB_URI, function (err, db) {
        if(err){
            console.warn(err.message);
        } else {
            var collection = db.collection('chat messages')
            var stream = collection.find().sort().limit(10).stream();
            stream.on('data', function (chat) { console.log('emitting chat'); socket.emit('chat', chat.content); });
        }
    });
  4. Add the next code snippet inside the "chat" event and before the socket.broadcast.emit('chat', msg); line:

    Speaking point: We want to connect to the database using the URI we have in the MONGODB_URI environment variable and insert the chat message received in the socket connection.

    (Code Snippet - NodeJsSocketIO-mongoDB-save-message)

    mongo.connect(process.env.MONGODB_URI, function (err, db) {
    	if (err) {
    		console.warn(err.message);
    	} else {
    		var collection = db.collection('chat messages');
    		collection.insert({ content: msg }, function (err, o) {
    			if (err) { console.warn(err.message); }
    			else { console.log("chat message inserted into db: " + msg); }
    		});
    	}
    });

## Summary ##

By completing this demo, you have learned how to create a simple chat application where communication between users and server is implemented using the Socket.IO module. You have also seen how to save/retrieve messages to/from a NoSQL database like Azure DocumentDB.