Book Image

MEAN Blueprints

By : Robert Onodi
Book Image

MEAN Blueprints

By: Robert Onodi

Overview of this book

The MEAN stack is a combination of the most popular web development frameworks available—MongoDB, Angular, Express, and Node.js used together to offer a powerful and comprehensive full stack web development solution. It is the modern day web dev alternative to the old LAMP stack. It works by allowing AngularJS to handle the front end, and selecting Mongo, Express, and Node to handle the back-end development, which makes increasing sense to forward-thinking web developers. The MEAN stack is great if you want to prototype complex web applications. This book will enable you to build a better foundation for your AngularJS apps. Each chapter covers a complete, single, advanced end-to-end project. You’ll learn how to build complex real-life applications with the MEAN stack and few more advanced projects. You will become familiar with WebSockets and build real-time web applications, as well as create auto-destructing entities. Later, we will combine server-side rendering techniques with a single page application approach. You’ll build a fun project and see how to work with monetary data in Mongo. You will also find out how to a build real-time e-commerce application. By the end of this book, you will be a lot more confident in developing real-time, complex web applications using the MEAN stack.
Table of Contents (13 chapters)
MEAN Blueprints
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

Setting up the base application


The best way to start is with a solid base. That's why we are going to focus on building the base structure of our application. A good base gives you modularity and flexibility and also files should be easily located by you and even your team members.

Always start with something simple and start building around it. As your application grows, you'll probably outgrow your initial application structure, so thinking ahead will bring you big benefits in the long run.

Folder structure

Before jumping in and building your features right away, you should take a moment and sketch out your initial application's structure. In the planning process, a pen and paper should always do it, but I've already saved some time and come up with an initial version:

app/
--controllers/
--middlewares/
--models/
--routes/
config/
--environments/
--strategies/
tests/
--integration/
--unit/
public/
--app/
--src/
--assets/
--typings/
--package.json
--tsconfig.json
--typings.json
package.json
server.js

Let's take a look at a more detailed explanation of our folder structure:

  • app: This folder contains all the server files used in the application:

    • controllers: This folder is going to store the application controllers, mainly the backend business logic.

    • middlewares: In this folder, we'll store all our pieces of functions that will manipulate the request and response object. A good example would be an authentication middleware.

    • models: This folder will store all the backend models.

    • routes: This folder will contain all the routing files, which is where we are going to define all Express routes.

  • config: All application configuration files go here:

    • environments: This folder contains files loaded according to the current environment

    • strategies: All your authentication strategies should go here

  • tests: This folder contains all the tests necessary to test the application backend logic:

    • integration: If something uses external modules, it's good practice to create an integration test

    • unit: This should contain tests for small units of code, such as password hashing

  • public: This should contain all the static files served by our application. I like this separation because it's easy to just tell another web server to handle our static files. Let's say you want nginx to handle static file serving:

    • app: This is our client-side application's folder. All compiled TypeScript files will go here. This folder should be automatically populated.

    • src: This folder contains all the client-side files used to build our application. We are going to use TypeScript to build our Angular application.

    • typings: This contains TypeScript definitions.

Server-side package.json

After setting up the initial folder structure, the next thing to do is to create the package.json file. This file will hold all the application's metadata and dependencies. The package.json file will be placed at the root of our project folder. The path should be contact-manager/package.json:

{
  "name": "mean-blueprints-contact-manager",
  "version": "0.0.9",
  "repository": {
    "type": "git",
    "url": "https://github.com/robert52/mean-blueprints-cm.git"
  },
  "engines": {
    "node": ">=4.4.3"
  },
  "scripts": {
    "start": "node app.js",
    "unit": "node_modules/.bin/mocha tests/unit/ --ui bdd --recursive --reporter spec --timeout 10000 --slow 900",
    "integration": "node_modules/.bin/mocha tests/integration/ --ui bdd --recursive --reporter spec --timeout 10000 --slow 900",
    "less": "node_modules/.bin/autoless public/assets/less public/assets/css --no-watch",
    "less-watch": "node_modules/.bin/autoless public/assets/less public/assets/css"
  },
  "dependencies": {
    "async": "^0.9.2",
    "body-parser": "^1.15.0",
    "connect-mongo": "^1.1.0",
    "express": "^4.13.4",
    "express-session": "^1.13.0",
    "lodash": "^3.10.1",
    "method-override": "^2.3.5",
    "mongoose": "^4.4.12",
    "passport": "^0.2.2",
    "passport-local": "^1.0.0",
    "serve-static": "^1.10.2"
  },
  "devDependencies": {
    "autoless": "^0.1.7",
    "chai": "^2.3.0",
    "chai-things": "^0.2.0",
    "mocha": "^2.4.5",
    "request": "^2.71.0"
  }
}

We added a few scripts to our package.json file to run our unit and integration tests and compile the Less files. You can always use npm to directly run different scripts instead of using build tools such as Grunt or Gulp.

At the time of writing this book, we are using the defined dependencies and their versions. This should do it for now. Let's install them using the following command:

$ npm install

You should see npm pulling a bunch of files and adding the necessary dependencies to the node_modules folder. Wait patiently until everything is installed and done. You will be returned to Command Prompt. Now you should see the node_modules folder created and with all the dependencies in place.

The first application file

Before everything, we need to create a simple configuration file for our environment. Let's create the file in the config folder at contact-manager/config/environments/development.js and add the following content:

'use strict';

module.exports = {
  port: 3000,
  hostname: '127.0.0.1',
  baseUrl: 'http://localhost:3000',
  mongodb: {
    uri: 'mongodb://localhost/cm_dev_db'
  },
  app: {
    name: 'Contact manager'
  },
  serveStatic: true,
  session: {
    type: 'mongo',
    secret: 'u+J%E^9!hx?piXLCfiMY.EDc',
    resave: false,
    saveUninitialized: true
  }
};

Now let's create the main server.js file for our application. This file will be the heart of our application. The file should be in the root of our folder, contact-manager/server.js. Start with the following lines of code:

'use strict';

// Get environment or set default environment to development
const ENV = process.env.NODE_ENV || 'development';
const DEFAULT_PORT = 3000;
const DEFAULT_HOSTNAME = '127.0.0.1';

const http = require('http');
const express = require('express');
const config = require('./config');
const app = express();

var server;

// Set express variables
app.set('config', config);
app.set('root', __dirname);
app.set('env', ENV);

require('./config/mongoose').init(app);
require('./config/models').init(app);
require('./config/passport').init(app);
require('./config/express').init(app);
require('./config/routes').init(app);

// Start the app if not loaded by another module
if (!module.parent) {
  server = http.createServer(app);
  server.listen(
    config.port || DEFAULT_PORT,
    config.hostname || DEFAULT_HOSTNAME,
    () => {
      console.log(`${config.app.name} is running`);
      console.log(`   listening on port: ${config.port}`);
      console.log(`   environment: ${ENV.toLowerCase()}`);
    }
  );
}

module.exports = app;

We define some of our main dependencies and initialize the necessary modules of our application. To modularize things, we are going to put each package of our stack into a separate configuration file. These configuration files will have some logic in them. I like to call them smart configuration files.

Don't worry! We are going to go through each config file one by one. Finally, we will export our Express app instance. If our module is not loaded by another module, for example, a test case, then we can safely start listening to incoming requests.

Creating the Express configuration file

We need to create a configuration file for Express. The file should be created in the config folder at contact-manager/config/express.js and we have to add the following lines of code:

'use strict';

const path = require('path');
const bodyParser = require('body-parser');
const methodOverride = require('method-override');
const serveStatic = require('serve-static');
const session = require('express-session');
const passport = require('passport');
const MongoStore = require('connect-mongo')(session);
const config = require('./index');

module.exports.init = initExpress;

function initExpress(app) {
  const root = app.get('root');
  const sessionOpts = {
    secret: config.session.secret,
    key: 'skey.sid',
    resave: config.session.resave,
    saveUninitialized: config.session.saveUninitialized
  };

  //common express configs
  app.use(bodyParser.urlencoded({ extended: true }));
  app.use(bodyParser.json());
  app.use(methodOverride());
  app.disable('x-powered-by');

  if (config.session.type === 'mongo') {
    sessionOpts.store = new MongoStore({
      url: config.mongodb.uri
    });
  }

  app.use(session(sessionOpts));
  app.use(passport.initialize());
  app.use(passport.session());

  app.use(function(req, res, next) {
    res.locals.app = config.app;

    next();
  });

  // always load static files if dev env
  if (config.serveStatic) {
    app.use(serveStatic(path.join(root, 'public')));
  }
};

You should be familiar with many lines from the preceding code by now, for example, setting the desired body parser of our Express application. Also, we set up the session management, and just in case we set to the server static files, we define the path to the server files.

In a production environment, you should use something different from the default in-memory storage for sessions. That's why we added a special session store, which will store data in MongoDB.

A good practice to get the global environment configuration file is to set a root config file that all application files will load, create a new file called contact-manager/config/index.js, and add this code to it:

'use strict';

var ENV = process.env.NODE_ENV || 'development';
var config = require('./environments/'+ENV.toLowerCase());

module.exports = config;

The preceding code will just load the necessary environment configuration file based on the NODE_ENV process environment variable. If the environment variable is not present, a default development state will be considered for the application. This is a good practice so that we don't make mistakes and connect to the wrong database.

Usually, the NODE_ENV variable can be set when you start your node server; for example, under Unix systems, you can run the following command:

$ NODE_ENV=production node server.js

Setting up mocha for testing

Before we implement any functionality, we are going to write tests for it. Mocha is a testing framework built on Node.js. This approach will give us the advantage of knowing what code we are going to write and testing our Node.js API before even writing a single line of the client application.

If you don't have Mocha, you can install it globally. If you want Mocha to be globally available in your command line, run the following command:

$ npm install -g mocha

Setting up Mongoose

In order to store data in MongoDB, we are going to use Mongoose. Mongoose provides an easy way to define schemas to model application data. We have already included mongoose in the package.json file, so it should be installed.

We need to create a config file for our mongoose library. Let's create our config file contact-manager/config/mongoose.js. First, we start by loading the Mongoose library, getting the appropriate environment config, and establishing a connection with the database. Add the following code to the mongoose.js file:

'use strict';

const mongoose = require('mongoose');
const config = require('./index');

module.exports.init = initMongoose;

function initMongoose(app) {
  mongoose.connect(config.mongodb.uri);

  // If the Node process ends, cleanup existing connections
  process.on('SIGINT', cleanup);
  process.on('SIGTERM', cleanup);
  process.on('SIGHUP', cleanup);

  if (app) {
    app.set('mongoose', mongoose);
  }

  return mongoose;
};

function cleanup() {
  mongoose.connection.close(function () {
    console.log('Closing DB connections and stopping the app. Bye bye.');
    process.exit(0);
  });
}

Also, we are using a cleanup() function to close all connections to the MongoDB database. The preceding code will export the necessary init() function used in the main server.js file.