The M.E.A.N stack

and the rise of the machines


https://form.io

<form.io> provides developers an easy drag & drop interface that creates both forms and the REST API's in one easy step!

Presentation Materials

Let's start with Angular.js

what makes it so darn special?

Angular.js Trend https://www.google.com/trends/explore#q=angularjs
M.E.A.N Stack Trend https://www.google.com/trends/explore#q=MEAN%20stack

Let's look at how the web has evolved.

Web 1.0

The static web

Web 2.0

The dynamic web

Web 2.0

The rise of the CMS

Web 3.0

The rise of the machines

Web 3.0

The rise of the machines

Developer Mandates

Applications must be made differently.

API-first Development

  • Build your REST platform first.
  • Your first "app" should be the API test.
  • Build your app or website on top of those API's.

Why Angular.js?

Forces us to develop our websites like a web application.

Next generation web application

Angular.js enables

Multi-Service Applications

Helping us go from this...

To this...

Micro-service architecture

To adapt, we should get M.E.A.N

What is a M.E.A.N web application?

  • M:
    MongoDB - A NoSQL database powered by JavaScript.
  • E:
    ExpressJS - A Node.js application framework.
  • A:
    AngularJS - A front-end javascript application framework.
  • N:
    Node.js - A server-side javascript application engine.

Why M.E.A.N?

  • Event-driven I/O for high concurrency.
  • Simple and repeatable architecture.
  • Large Open Source community
  • Full stack JavaScript

An entire ecosystem of JavaScript

Javascript FTW!

How we will defeat the "rise of the machines".

Let's build an App!

  • A movie trailer application
  • Full CRUD capabilities
  • Total separation between API server and AngularJS front-end.

Building the Server

"API first"

Setup Node.js project

Initialize the app
mkdir server
cd server
npm init

Installing Node.js modules


							npm install --save express
npm install --save mongoose
npm install --save resourcejs
npm install --save method-override
npm install --save body-parser
npm install --save lodash

Writing the bootstrap

server/index.js
var express = require('express');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var _ = require('lodash');
// Create the application.
var app = express();
// Add Middleware necessary for REST API's
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(methodOverride('X-HTTP-Method-Override'));
// Connect to MongoDB
mongoose.connect('mongodb://localhost/meanapp');
mongoose.connection.once('open', function() {
								  console.log('Listening on port 3000...');
  app.listen(3000);
});

Running the app

Shell
  node index.js
						
Output
  Listening on port 3000...
						

If you to to http://localhost:3000 you will see the server is running, but nothing shows up. This is expected.

Adding Middleware

Let's add CORS support for RESTful interfaces.

server/index.js
...
...
app.use(methodOverride('X-HTTP-Method-Override'));

// CORS Support
app.use(function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  next();
});

mongoose.connect('mongodb://localhost/meanapp');
...
...
						

Using app.use for content routes

server/index.js
...
...
app.use('/hello', function(req, res, next) {
  res.send('Hello World!');
  next();
});
...
...
						

Now go to http://localhost:3000/hello in your browser.

This is the fundamental idea at registering REST endpoints at certain paths.

Creating a Model

New file @ server/models/Movie.js
var mongoose = require('mongoose');

// Create the MovieSchema.
var MovieSchema = new mongoose.Schema({  title: {
    type: String,
    required: true
  },
  url: {
    type: String,
    required: true
  }
});
// Export the model.
module.exports = mongoose.model('movie', MovieSchema);

Index of Models

New file @ server/models/index.js
module.exports = {
  movie: require('./Movie')
};

Loading the Models

server/index.js
...
...
mongoose.connection.once('open', function() {

  // Load the models.
  app.models = require('./models/index');

...
...

Creating a Controller

New file @ server/controllers/MovieController.js
var Resource = require('resourcejs');
module.exports = function(app, route) {
  // Setup the controller for REST
  Resource(app, '', route, app.models.movie).rest();
  // Return middleware.
  return function(req, res, next) {
    next();
  };
};

Creating the routes

New file @ server/routes.js
module.exports = {
  'movie': require('./controllers/MovieController')
};

Registering the routes

server/index.js
...
...

app.models = require('./models/index');

// Load the routes.
var routes = require('./routes');_.each(routes, function(controller, route) {  app.use(route, controller(app, route));
});

...
...

Check your code

https://github.com/travist/meanapp/tree/resourcejs/server

Run the code

cd server
node index.js

Use Postman to test

Building the Client

in AngularJS

Bootstrapping the client with Yeoman

http://yeoman.io/

mkdir clientcd clientnpm install -g yonpm install -g generator-angularyo angular

Generate the movies route

yo angular:route movies

Add Movies to the navigation

client/app/index.html

							

Create a table list of Movies

app/views/movies.html<table class="table table-striped">
  <thead>
    <th>Title</th>
    <th>URL</th>
  </thead>
  <tbody>    <tr ng-repeat="movie in movies">      <td>{{ movie.title }}</td>
      <td>{{ movie.url }}</td>    </tr>
  </tbody>
</table>
						

Add movies to the scope

app/scripts/controllers/movies.js
angular.module('clientApp')
.controller('MoviesCtrl', function ($scope) {
  $scope.movies = [
    {
      title: 'A New Hope',
      url: 'http://youtube.com/embed/1g3_CFmnU7k'
    },
    {
      title: 'The Empire Strikes Back',
      url: 'http://youtube.com/embed/96v4XraJEPI'
    },
    {
      title: 'Return of the Jedi',
      url: 'http://youtube.com/embed/5UfA_aKBGMc'
    }
  ];
});
						

Let's hook up $scope.movies with our server.

Use Bower to add Restangular to your project


bower install --save restangular
						

Verify it adds it to app/index.html



						

Adding and Configuring Restangular

app/scripts/app.js
angular
.module('clientApp', [
  'ngRoute',
  'restangular'
])
.config(function (
  $routeProvider,  RestangularProvider
) {
  RestangularProvider.setBaseUrl('http://localhost:3000');
  ...
  ...

Adding Movie factories

app/scripts/app.js
.factory('MovieRestangular', function(Restangular) {
  return Restangular.withConfig(function(RestangularConfigurer) {
    RestangularConfigurer.setRestangularFields({
      id: '_id'
    });
  });
})
.factory('Movie', function(MovieRestangular) {
  return MovieRestangular.service('movie');
})
						

Query the list of movies

app/controllers/movies.js
.controller('MoviesCtrl', function (
  $scope,
  Movie
) {
  $scope.movies = Movie.getList().$object;
});
						

Adding CRUD capabilities


yo angular:route movie-add --uri=create/movie
yo angular:route movie-view --uri=movie/:id
yo angular:route movie-delete --uri=movie/:id/delete
yo angular:route movie-edit --uri=movie/:id/edit
						

CREATE

app/views/movie-add.html
app/scripts/controllers/movie-add.js
.controller('MovieAddCtrl', function (
  $scope,
  Movie,
  $location
) {
  $scope.movie = {};
  $scope.saveMovie = function() {
    Movie.post($scope.movie).then(function() {
      $location.path('/movies');
    });
  };
});
						

READ

app/views/movie-view.html

{{ movie.title }}

{{ movie.url }}

app/scripts/controllers/movie-view.js
.controller('MovieViewCtrl', function (
  $scope,
  $routeParams,
  Movie
) {
  $scope.viewMovie = true;
  $scope.movie = Movie.one($routeParams.id).get().$object;
});
						

UPDATE

app/views/movie-edit.html

						
app/scripts/controllers/movie-edit.js
.controller('MovieEditCtrl', function (
  $scope,
  $routeParams,
  Movie,
  $location
) {
  $scope.editMovie = true;
  $scope.movie = {};
  Movie.one($routeParams.id).get().then(function(movie) {
    $scope.movie = movie;
    $scope.saveMovie = function() {
      $scope.movie.save().then(function() {
        $location.path('/movie/' + $routeParams.id);
      });
    };
  });
});
						

DELETE

app/views/movie-delete.html

Are you sure you wish to delete the movie {{ movie.title }}?

app/scripts/controllers/movie-delete.js
.controller('MovieDeleteCtrl', function (
  $scope,
  $routeParams,
  Movie,
  $location
) {
  $scope.movie = Movie.one($routeParams.id).get().$object;
  $scope.deleteMovie = function() {
    $scope.movie.remove().then(function() {
      $location.path('/movies');
    });
  };
  $scope.back = function() {
    $location.path('/movie/' + $routeParams.id);
  };
});
						

Creating a navigation tabs

Create: app/views/movie-nav.html

						
app/views/movie-view.html

{{ movie.title }}

{{ movie.url }}

app/views/movie-edit.html


						

Adding links to the movies page.

app/views/movies.html
 Create Movie

Title URL Operations
{{ movie.title }} {{ movie.url }}

Adding a YouTube Player directive

app/scripts/app.js
.directive('youtube', function() {
  return {
    restrict: 'E',
    scope: {
      src: '='
    },
    templateUrl: 'views/youtube.html'
  };
}).filter('trusted', function ($sce) {
  return function(url) {
    return $sce.trustAsResourceUrl(url);
  };
});
						

Adding a YouTube Player directive

app/views/youtube.html
app/views/movie-view.html

{{ movie.title }}

Amazing!

THANK YOU!