Building A New Web App Written In JavaScript/TypeScript

In part one of this series, we discussed some of the components that make up a complete application written in JavaScript/TypeScript.  In this part of the series, we will build a simple chat application, illustrating how this works.  More specifically, we’re going to build the server for the chat app, and Part Three will focus on the front end of the app.

Clone From GitHub

You can find my GitHub repository for this at the link below.
https://github.com/rolson-intertech/chat-app-server

If you do this, just be sure to execute the following command at the command line to fully set it up after cloning.

npm install

IDE (VS Code)

There are a number of IDEs available to use.  My favorite IDE for web development is Microsoft’s Visual Studio Code.  It’s free, and works on all platforms.  You can install it from:
https://code.visualstudio.com/

NodeJS & NPM

If you haven’t already done so, you’ll need to install NodeJS on your machine.  NPM installs with NodeJS, so you’ll be killing two birds with one stone here.  You can find installations here:
https://nodejs.org 

MongoDB

For our database implementation, we’re going to use MongoDB.  MongoDB is a NoSQL database, and works seamlessly with JSON annotation.  You can find installation files for MongoDB here:
https://www.mongodb.com/try/download/community

This will install MongoDB as a service on your machine, much like MS SQL Server does.  While we won’t be discussing MongoDB in much detail, you can get a free GUI to interact with MongoDB at:
https://www.mongodb.com/try/download/compass 

TypeScript

TypeScript is installed as part of your development dependencies, making this step technically unnecessary.  This local installation occurs, because TypeScript is specified in the devDependencies section of our package.json file (see below).

I prefer to have a global installation of TypeScript however, because I like to have my tools conveniently on hand when I need to do something out of the ordinary.

To install TypeScript globally execute the following line in your command prompt:
npm install -g typescript

NPM Installs: Local Vs Global

It’s important to describe what happens when you install something through NPM locally versus globally.  Local installations are placed in the /node_modules/ folder of your project.  Global installations are installed at the system level which varies based on your OS, and is typically in your systems PATH.  What this means is, you can run a globally installed application from almost anywhere in you command line, but not so for local installations.

So, you might be thinking we should always install certain tools globally, and avoid local installs.  This would be a mistake for a few reasons though. 

First, projects typically depend on a specific version of a tool or dependency.  I have made the mistake many times of running a global installation of TypeScript’s compiler on a project, that required a newer (or specific) version of Typescript to compile.  I’d end up with errors, and it would take me a while to realize the problem.

Second, since a global installation cannot be defined in your project (package.json file), you couldn’t use NPM to automatically handle all of the installation details of your application when you started development on a different machine.

So how can we make using a local tool installation convenient?  NPM’s scripts automatically attempt to execute your tools locally before globally.  As for our project, having our NPM macro run TSC (TypeScript’s compiler) ensures that the local version gets ran, ignoring the global installation. 

Project Setup

Project Folders

We’ll want to create a folder for our project.  Where you place it and what you call it isn’t important.  I would, however, recommend creating a folder to house both our server and front-end projects for convenience.

Inside the folder for your server project, add another folder named “src”.  This is where we’ll place all of our TypeScript source files.

NPM Project Installation

Next, we’re going to cheat a little by letting NPM setup most of our project for us.  Create a new file in our server folder called “package.json”, and paste the following code snippet inside.  Then, from your server folder, in your command prompt, run the command “npm install” and watch the magic. 

Individual Package Installation

I purposely omitted MongoDB from this package.json file for two reasons.  First, since you may have just installed a new version of MongoDB, I wanted to be sure you have the right references.  Secondly, we need to learn how to install a package to your project.  We’ll install MongoDB’s type definitions as a “dev dependency”, since they are not required in the production build.  Though, this is usually just a schematic detail, it’s best to follow the rules.

To install MongoDB drivers, run the following at your command line, from the folder for your server project:
npm install –save mongodb

To install MongoDB type definitions, run the following at your command line from the same folder:
npm install –save-dev @types/mongodb

You will notice that, because we added -save and -save-dev switches, these items are added to the dependency sections in your package.json file for you.

TypeScript Configuration

We’ll need to tell TypeScript how to behave.  Create a new file named “tsconfig.json”, and paste the code snippet on the right inside.

No Explanation:

  1. Create a folder for your project.
  2. Add a new file named “project.json”, and paste following code snippet inside.
  3. Open a command prompt, and navigate to your new folder.
  4. Execute: npm install
  5. Execute: npm install –save mongodb
  6. Execute: npm install –save-dev @types/mongodb
  7. Add a new file named “tsconfig.json” and paste the following code snippet inside.
  8. Execute mkdir src.
Promises

In a nutshell, a promise is a convenient way of working with asynchronous code without using callback functions.

Promises are used in various places of this project and the client project, so having an idea of what they do is important.

If you’re not familiar with promises, check out my blog post on the topic.  You can find it here.

package.json

{
  "name": "chat-server",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "start": "tsc && node ./dist/index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "express": "^4.17.1",
    "socket.io": "^2.3.0"
  },
  "devDependencies": {
    "@types/express": "^4.17.7",
    "@types/node": "^14.0.26",
    "@types/socket.io": "^2.1.10",
    "typescript": "^3.9.7"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "strictNullChecks": false,
    "noImplicitAny": false,
    "esModuleInterop": true
  }
}

Server Goals & Components

Before starting any project, it’s important to understand our goals and architecture we expect to use.  Let’s take a moment and do that now.

Server Implementation Goals

  • Facilitate real-time chat API to support our front end.
  • Provide persistence for our chat, allowing newcomers to the chat to get the full chat contents before they arrived.
  • Create an extensible implementation that allows for easy expansion in the future.
  • Use an object oriented approach.

The idea will be to create a master server class that can take any number of server plugins, which add functionality.  The master server will basically be a shell to provide basic support for plugins and orchestrate their initialization.  In addition to the obvious implementations of the master server and the plugins, we’ll create an IServerPlugin interface to define what a plugin must implement.

Component Details

Below are an overview of the three 3rd party components we’ll be using.  These explanations aren’t in depth, but help you understand their function.

 

MongoDB

We’ll want our chat app to hold past messages so that newcomers can scroll back and see messages they’ve missed.  MongoDB is an incredibly easy-to-use NoSQL database, letting your interactions with the database define the data structures.  Put a different way, we don’t need to setup the database in order to use it.  When we add data to a table (called “collections” in MongoDB), it doesn’t need to conform to a specific schema.  It’s simply placed there, and an ObjectID is returned to you.  In fact, when a table (collection) doesn’t exist, it’s created on the fly.

If you’re coming from the RDB world, you may be thinking “this is heresy!”  Yeah, maybe… but hold up there cowboy!  MongoDB can be setup to validate your data against schemas and perform more traditional checks.  If that’s not good enough, MongoDB is not a requirement for this sort of development.  NodeJS has drivers for all popular relational (and non-relational) database flavors.

The only thing I’d warn against is the notion that MongoDB is inefficient or doesn’t handle data relationships.  Like relational databases, you can define complex indices on your tables (collections), as well as perform relational queries on that data.  Queries can even be recursive, allow you to build things like a parent/child tree from a single table.

Whether or not you’re a skeptic, you can have a look at their documentation at: https://docs.mongodb.com/drivers

ExpressJS

While NodeJS is fully capable of handling web requests, ExpressJS is a popular framework that makes web server development easier in NodeJS.

ExpressJS uses a pipeline approach to adding routes and middleware functions.  Wait, that sounds too techie… Let’s rephrase…

We add functions (middleware) to Express to handle the server requests, in the order we want those functions to be called on each request.  Some functions can “do something”, like perform a security check on the request, while other can respond directly to the request, like send a file or web page.  Further, middleware can be configured to be applied to only specific url paths, or all of them, as needed.

There are many ways to configure routes, but we will use 2 specific scenarios:

  1. Register API routes for specific HTTP method (get, post, etc).
  2. Add express’ off-the-shelf web server middleware.

 You can find more information on the ExpressJS website: http://expressjs.com

Socket.IO

Socket.IO provides us a convenient way to receive and send real-time messages to our application.  We want to keep things simple, so we won’t dive too deep into Socket.IO.

The basic approach is to setup Socket.IO to listen for connections from the client, and attach message handlers to those connections for arbitrary data types (i.e. a new chat message).  A message type can be any arbitrary string with any arbitrary data passed to the function.  In our case, we’ll create a message type that handles a specific interface/type for our “chat messages”.  The handler will broadcast that message back to all connections and be displayed to the users by the client (discussed in part 3).

You can find the documentation and more information on the Socket.IO website: https://socket.io

 

Server Source Files

    MasterServer

    The Master Server will act as an extensible container for server plugins (IServerPlugin).  On its own, it doesn’t do much aside from tying the plugins together and providing common setup for the express server, including a single, shared, instance of the ExpressJS server (application).  This is necessary, because only one instance of the Express.Application is allowed per port, and our plugins will all want to use the same port on our server.

    Use of the MasterServer is fairly simple.  We create an instance, and supply the plugins and port number to it.  Then we call listen.  It will initialize the plugins and then ask them to register their endpoints (if any).  When the listen method is called, everything should just work!

    I’d like to point out that registering the URL encoder middleware (seen in the code below) wasn’t actually needed for this tutorial.  I decided to include it, because anyone wanting to extend the demo further might find the need for it.

    Going against convention, I added the IServerPlugin interface to this file to keep the number of files on this project small.  I would normally place this type definition in its own file though.

    master.server.ts

    import Express from 'express';
    import bodyParser from "body-parser";
    import * as http from 'http';
    
    /** General form of a server plugin. */
    export interface IServerPlugin {
    
        /** The MasterServer that owns this plugin. */
        masterServer: MasterServer;
    
        /** Allows this plugin to initialize itself */
        initialize(): void;
    
        /** Allows this plugin to register its routes with the application. */
        registerRoutes(): void;
    }
    
    /** Responsible for implementing multiple plugins that supports our overall server capabilities. */
    export class MasterServer {
        constructor(portNumber: number, plugins: Array<IServerPlugin>) {
            this.plugins = plugins;
            this.portNumber = portNumber;
    
            // Perform initialization.
            this.initialize();
        }
    
        private _httpServer: http.Server;
        /** Returns the underlying node HTTP server used for communications with the client. */
        get httpServer(): http.Server {
            return this._httpServer;
        }
    
        private _expressApp: Express.Application;
        /** The Express application that handles the pipeline and server calls. */
        get expressApp(): Express.Application {
            return this._expressApp;
        }
    
        /** Array of plugins that add arbitrary functionality to the server. 
         *   This is set in our constructor.    */
        private readonly plugins: Array<IServerPlugin>;
    
        /** The port number that our server runs on. */
        readonly portNumber: number;
    
        /** Initializes this server object, and all of the plugins. */
        private initialize(): void {
            // Create the new express app.
            this._expressApp = require('express')();
    
            // Socket.IO needs access to the underlying http server.  This provides that.
            this._httpServer = require('http').createServer(this.expressApp);
    
            // Without this, our requests don't have a body when received from the clients.
            //  This allows for special cases, the body stream may not simply be text.
            this.expressApp.use(bodyParser.text());
    
            /** This is responsible for taking parameters in URL queries, and 
             *   placing them in the Params of our requests when they are received. */
            this.expressApp.use(Express.urlencoded({}));
    
            // We just want to print something to the console with every request here.
            this.expressApp.use((req, res, next) => {
                // Log this to the console.
                console.log(`Received Request: ${req.path}, ${req.ip}`);
    
                // This tells express that our function (middleware) didn't completely handle the request and to
                //  keep searching for a function that does.  Without it, no other middleware is called after this.
                next();
            });
    
            // Initialize the plugins.
            this.initializePlugins();
    
            // Register their endpoints.
            this.registerPluginEndpoints();
        }
    
        /** Perform initialization of the plugins, allowing them to register their routes and perform
         *   any other setup needed. */
        private initializePlugins(): void {
            this.plugins.forEach(p => {
                // Set the owner on this plugin, and then let it initialize.
                p.masterServer = this;
                console.log(`Initializing Plugin: ${p.constructor.name}`);
                p.initialize();
            });
        }
    
        /** Registers all endpoints on the plugins. */
        private registerPluginEndpoints(): void {
            this.plugins.forEach(p => {
                console.log(`Registering routes for: ${p.constructor.name}`);
                p.registerRoutes();
            });
        }
    
        /** Starts the server listening for server requests. */
        listen(): void {
            this.httpServer.listen(this.portNumber);
        }
    }

    Plugins

    All plugins derive from the IServerPlugin interface as we’ve already seen in the master.server.ts file.  This is the minimal shape required to work with the MasterServer.

    We are using two plugins.  The WebServerPlugin and ChatServerPlugin.  This provides a separation of concerns between normal web server functions and real time chat functions.  Further, it is completely modular, allowing us to remove either one and still support the purposes they were designed for through the MasterServer.

    WebServerPlugin

    The WebServerPlugin handles basic web file server functionality, and is as simple as it gets.  We provide the path on the server to our static content, which will be our front end created in Part 3 of this series.  It requires no initialization.  It will register a route to the root of the site, and assume index.html is the default file to server here.  It then registers the static content as the default place to look for any server requests not handled elsewhere.   Specifically, the resources referenced by our web page/client.

    As we’ll see later, it’s important that the WebServerPlugin is defined last in the list of plugins for the MasterServer.  This is because the static route functionality will automatically respond with a 404 (not found) error if a request is made for a resource that does not exist. 

    web-server.plugin.ts

    import { MasterServer, IServerPlugin } from "./master.server";
    import Express from 'express';
    import * as path from 'path';
    
    /** Server plugin that serves static files.  NOTE: This should be the LAST plugin
     *   registered with the master server, because it will handle 404 errors when routes are not found. */
    export class WebServerPlugin implements IServerPlugin {
        constructor(contentPath: string) {
            this.contentPath = contentPath;
    
            console.log(`Serving files from: ${contentPath}`);
        }
    
        /** Full path to the root folder of files that this webserver will be serving from.
         *   NOTE: This will be our client's output files. */
        private readonly contentPath: string;
    
        /** The MasterServer that owns this plugin. */
        masterServer: MasterServer;
    
        initialize(): void {
            // Do nothing.
        }
    
        registerRoutes(): void {
            // We want to direct calls to the root of our site to our index.html file.
            //  NOTE: This MUST come before the registration of EXPRESS.static middleware below, or it won't work.
            this.masterServer.expressApp.get('/', (req, res) => {
                res.sendFile(path.join(this.contentPath, 'index.html'));
            });
    
            // This will cause express to serve file requests from matching files found in the contentPath.
            this.masterServer.expressApp.get('*', Express.static(this.contentPath));
        }
    }

    ChatServerPlugin

    The ChatServerPlugin provides two basic functions.  First, it handles the real time messaging to clients through Socket.IO.  Second, it provides database support for our messages.  It will respond to an API request to retrieve all messages from the database using Express’s standard routing functions.  In addition, it uses Socket.IO to listen and respond to messages from the client.  When they are received, the messages are re-broadcast to all clients connected to the socket.

    A smarter implementation of this would allow us to only retrieve a few messages at a time, but that would add more complexity to the front and back ends.  We want to keep this fairly simple.

    As stated in the source’s comments, we could have used Socket.IO to handle the request to retrieve all messages.  I’ve implemented this as an API call however, to illustrate how API’s are created.

    import { MasterServer, IServerPlugin } from "./master.server";
    import { convertDates } from "./shared-definitions";
    import { EP_SEND_NEW_MESSAGE } from "./shared-definitions";
    import { EP_GET_ALL_MESSAGES, MSG_SEND_MESSAGE, MSG_MESSAGE_RECEIVED, IChatMessage } from "./shared-definitions";
    import { MongoClient, Db, ObjectId } from "mongodb";
    import * as socketio from 'socket.io';
    
    /** Name of the database collection (table) that holds chat messages. */
    const MESSAGE_COLLECTION_NAME = 'messages';
    
    /** Server Plugin that handles chat message handling. */
    export class ChatServerPlugin implements IServerPlugin {
        constructor(dbConnectionString: string) {
            this.dbConnectionString = dbConnectionString;
        }
    
        /** Connection string to our database. */
        readonly dbConnectionString: string;
    
        /** Database client reference obtained during initialization. */
        private dbClient: MongoClient;
    
        /** Database reference to our actual database. */
        private db: Db;
    
        masterServer: MasterServer;
    
        initialize(): void {
            // Connect our client to the database.
            this.dbClient = new MongoClient(this.dbConnectionString);
    
            // Connect the client to the server.
            this.dbClient.connect().then(() => {
                // Get the database reference for this.
                this.db = this.dbClient.db();
            });
    
            // Setup the socket server to respond to realtime messaging.
            this.setupSocketServer();
        }
    
        registerRoutes(): void {
            // Register a route to get all messages from the database.
            //  This will only respond to request for the POST method, and ignore others.
            //  NOTE: This COULD be implemented using sockets, but it illustrates how API
            //  calls are made.
            this.masterServer.expressApp.post(EP_GET_ALL_MESSAGES, (req, res) => {
                // Get the messages from the database.
                this.getAllMessages().then(result => {
                    // Return them to the response.
                    res.send(result);
                })
            });
    
            // Register a route for the user to send a message to the chat.
            this.masterServer.expressApp.post(EP_SEND_NEW_MESSAGE, (req, res) => {
                // Get the body as a message object.
                let message = <IChatMessage>JSON.parse(req.body);
    
                // Convert date strings on this object to dates.
                convertDates(message);
    
                // Broadcast the message to others.
                this.socket.emit(MSG_MESSAGE_RECEIVED, message);
    
                // Save this message to the database.
                this.createMessage(message).then(() => {
                    res.end();
                });
    
            });
        }
    
        /** Sets up the Socket.IO functionality, along with callbacks for client requests. */
        private setupSocketServer(): void {
            // Create the socket server.
            this.socket = require('socket.io')(this.masterServer.httpServer);
    
            // When we receive a connection, we need to setup our
            //  callbacks to handlers for those listening.
            this.socket.on('connect', socket => {
                // When the client sends a chat message, we need to broadcast it to everyone, including
                //  the sender.
                socket.on(MSG_SEND_MESSAGE, (message: IChatMessage) => {
                    // Store this in the database for later.  Might as well wait until
                    //  it has a valid object ID before we send it too.
                    this.createMessage(message).then(newId => {
                        // Set the ID on the new message.  It should be a string for clients, since
                        //  they cannot reference the MongoDB api. 
                        message._id = newId.toHexString();
    
                        // Broadcast the message to all other clients.
                        this.socket.send(MSG_MESSAGE_RECEIVED, message);
                    });
                });
            });
        }
    
        /** The socket.io server that handles realtime messaging. */
        private socket: socketio.Server;
    
        /** Returns all messages in the database. */
        getAllMessages(): Promise<Array<IChatMessage>> {
            // This will return all messages from the database.
            return this.db.collection(MESSAGE_COLLECTION_NAME).find({}).toArray();
        }
    
        /** Stores a new message in the database, and returns its object ID. 
         *   This will be called by the chat server, and not handled directly. */
        createMessage(newMessage: IChatMessage): Promise<ObjectId> {
            const res = this.db.collection(MESSAGE_COLLECTION_NAME).insertOne(newMessage);
            return res.then(result => {
                // Return just the object ID from the result.
                return result.insertedId;
            });
        }
    }

    Shared Definitions

    A few definitions and constants need to be shared between the front end and the back end.  I would normally create a folder for these and separate the code into different files, but having a single file for this example makes things easier to follow along.

    Four basic items are included here.  API end point paths, Socket.IO message types, IChatMessage interface, and some helper functions for dates.

    Another item I’d like to point out is that the _id property of the IChatMessage has different types in the server and the client implementations.  Since the MongoDB library cannot be referenced from the client (browser), we cannot provide the ObjectID type to it.  For this reason, we provide the ObjectID to the client in the form of a string.  If we were to create a call that received an object ID from the server, we would want to convert these back to ObjectIDs for use in the database.  This is important, because TypeScript doesn’t provide runtime type checking and strings cannot be compared directly to ObjectIDs in MongoDB queries.

    I’m using the most basic approach in this demonstration, but more sophisticated methods could be employed here, since real life implementations may include object IDs in different fields or even fields of nested data structures.

    The two date functions exist, because when converting dates to JSON, they become strings, and don’t convert back when parsed.  The isDateString function returns true if a string is passed to it that matches the regular expression pattern for a date in JSON.  Otherwise it returns false.  The convertDates method will examine all properties of a given object (or array), and convert any date strings to date objects.  It will perform this action recursively on any property that is an non-null object type.

    shared-definitions.ts

    /* API endpoints for our server to respond to.  NOTE that these values are completely arbitrary. */
    export const EP_GET_ALL_MESSAGES = '/api/get-all-messages';
    export const EP_SEND_NEW_MESSAGE = '/api/send-message';
    
    /* Socket.IO messages to respond to.  These values are also completely arbitrary. */
    export const MSG_SEND_MESSAGE = 'send-message';
    export const MSG_MESSAGE_RECEIVED = 'message-received';
    
    
    /** The form of a chat message. */
    export interface IChatMessage {
        /** Database ID of the message.
         *   NOTE: We're cheating a little here.  We want to share this file
         *   with the client, and the client can't reference MongoDB.  We pass this
         *   to the client as a string, and covert it back on the server to an ObjectID. */
        _id?: any;
    
        /** Date/Time that the message was made. */
        dateTime: Date;
    
        /** The name of the person who sent the message. */
        senderName: string;
    
        /** The message body. */
        message: string;
    }
    
    /** Returns a boolean value indicating whether or not a specified value
     *   is a string that holds a JSON date/time. */
    export function isDateString(val: any): boolean {
        // It must be a string to check it.
        if (typeof val !== 'string') {
            return false;
        }
    
        // Check its pattern.
        return /d{4}-d{2}-d{2}Td{2}:d{2}:d{2}.d{3}Z/.test(val);
    }
    
    /** Converts any string property that is formatted date/time, on a specified object,
     *   or nested objects, to an actual date. */
    export function convertDates(target: any): void {
        // Arrays work differently.  Check this explicitly.
        if (Array.isArray(target)) {
            // Convert properties on this array.
            for (var i = 0; i < target.length; i++) {
                if (isDateString(target[i])) {
                    // Convert this value.
                    target[i] = new Date(target[i]);
                } else if (target[i] != null && typeof target[i] === 'object') {
                    // Convert dates on this this nested item.
                    convertDates(target[i]);
                }
            }
    
            // We can only work on non-null objects.
        } else if (target != null && typeof target === 'object') {
            // Check the properties on the target.
            for (var n in target) {
                if (isDateString(target[n])) {
                    // Convert it.
                    target[n] = new Date(target[n]);
                } else if (typeof target[n] === 'object' && target[n] != null) {
                    // Convert nested values on this object.
                    convertDates(target[n]);
                }
            }
        }
    
    }

    Server Entry Point

    Our final source file is our server’s entry point.  The compiled form of this file is what node executes to start running our server.

    As you might expect, this is creating instances of the master server and plugins we just discussed and starts listening for server requests.

    You’ll note that we’re using port number 3001 for our implementation.  When we start development of the client, port 3000 is commonly used by the client’s development server.  We will inform the development server about our API server running on port 3001, so it can route API calls there.  As we’ll discuss in Part 3, the web server portion of our implementation will not be used during development, since the front-end tools can recompile our work on the fly and refresh the browser most efficiently.

    When it comes time to release for production, we use port 80 for everything, as well as use our web server implementation. 

    index.ts

    
    import { WebServerPlugin } from "./web-server.plugin";
    import { MasterServer } from "./master.server";
    import { ChatServerPlugin } from "./chat-server.plugin";
    import * as path from 'path';
    
    /** Connection string to our MongoDB. */
    const mongoDbConnectionString = 'mongodb://localhost/chat-app';
    
    /** Path to our client files.  This should be the folder with the index.html,
     *   which is in the *output* of our client application.  (Not the public folder.) */
    const staticFileLocation = path.join(__dirname, '../..', 'client/build');
    
    // Create our plugins for use in the master server.
    const chatPlugin = new ChatServerPlugin(mongoDbConnectionString);
    const webServerPlugin = new WebServerPlugin(staticFileLocation);
    
    // Create and initialize the master server.
    const masterServer = new MasterServer(
        // When developing in react, this port number should not be the same as the dev server.
        //  It will act as a proxy server during development, but in production, we'll use port 80, and it will
        //  indeed serve the front-end files.
        3001,
        [chatPlugin,
            // WARNING: Make sure this is always the LAST item in the plugin set, or it will consider
            //  any requests it doesn't handle as 404 requests (not found).
            webServerPlugin]);
    
    // Start listening for requests.
    masterServer.listen();

    Compile

    Compilation and execution is pretty simple.  Using NPM’s scripts, we can simply use the following command at the command prompt:
    npm start

    Execution & Conclusion

    Unfortunately, we suffer from a “chicken or the egg” scenario, because without a client, we can’t test the server easily.  Without the server, the client can’t fully function.  To this point, you’ve got a running server application though, and we’ll put it to use in Part 3 of this tutorial.

    About Intertech

    Founded in 1991, Intertech delivers software development consulting to Fortune 500, Government, and Leading Technology institutions, along with real-world based corporate education services. Whether you are a company looking to partner with a team of technology leaders who provide solutions, mentor staff and add true business value, or a developer interested in working for a company that invests in its employees, we’d like to meet you. Learn more about us.