This document provides notes from a presentation on building applications with the MEAN stack. It discusses the typical structure of a MEAN application with MongoDB, Express, AngularJS and Node.js. It covers Angular fundamentals like routing, templates, controllers and directives. It also discusses more advanced topics like logging in MEAN applications, minification, and using MongoDB _id fields.
1 of 79
More Related Content
MEAN - Notes from the field (Full-Stack Development with Javascript)
1. MEAN - Notes from the field
Chris Clarke
Hydrahack Birmingham
18th March 2014
Full-Stack Development with Javascript
7. Angular AppAngular App
Typical MEAN Shape
DBDB
APIAPI
Server side
pages
Server side
pages
StaticsStatics
JSON
JSON
HTMLHTML
JSON
Client side
Server side
14. Angular 101
• Single page web app framework, by Google
• Extends HTML vocabulary to provide dynamic
views
• Broadly MVC (more accurately MVVM)
• Bi-directional data binding to HTML
53. Angular AppAngular App
Typical MEAN Shape
DBDB
APIAPI
Server side
pages
Server side
pages
StaticsStatics
JSON
JSON
HTMLHTML
JSON
Client side
Server side
61. Security
• APIs secured with OAuth 2.0 Bearer tokens
• Tokens obtained with a key/secret
• If your app is downloaded and run on the client,
where do you put the secret?
62. Security
• Have node return the OAuth token as JSON behind
a login barrier
• Angular requests this JSON when a route that
requires login is first requsted
• If status != 200, Angular app redirects browser to
login page
• User logs in, repeat
63. Security
• Dealing with tokens on every service call is a PITA
• Tokens expiring is normal
• Deal with it globally using a couple of advanced
$http features
65. $httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
return promise.then(function(response) {
return response; // no action, was successful
}, function (response) {
// error - was it 401 or something else?
if (response.status===401 && response.data.error && response.data.error === "invalid_token") {
var deferred = $q.defer(); // defer until we can re-request a new token
// Get a new token... (cannot inject $http directly as will cause a circular ref)
$injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK')
.then(function(loginResponse) {
if (loginResponse.data) {
$rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope
// now let's retry the original request
$injector.get("$http")(response.config).then(function(response) {
// we have a successful response - resolve it using deferred
deferred.resolve(response);
},function(response) {
deferred.reject(); // something went wrong
});
} else {
deferred.reject(); // login.json didn't give us data
}
}, function(response) {
deferred.reject(); // token retry failed, redirect so user can login again
$location.path('/user/sign/in');
return;
});
return deferred.promise; // return the deferred promise
}
return $q.reject(response); // not a recoverable error
});
};
});
66. $httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
return promise.then(function(response) {
return response; // no action, was successful
}, function (response) {
// error - was it 401 or something else?
if (response.status===401 && response.data.error && response.data.error === "invalid_token") {
var deferred = $q.defer(); // defer until we can re-request a new token
// Get a new token... (cannot inject $http directly as will cause a circular ref)
$injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK')
.then(function(loginResponse) {
if (loginResponse.data) {
$rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope
// now let's retry the original request
$injector.get("$http")(response.config).then(function(response) {
// we have a successful response - resolve it using deferred
deferred.resolve(response);
},function(response) {
deferred.reject(); // something went wrong
});
} else {
deferred.reject(); // login.json didn't give us data
}
}, function(response) {
deferred.reject(); // token retry failed, redirect so user can login again
$location.path('/user/sign/in');
return;
});
return deferred.promise; // return the deferred promise
}
return $q.reject(response); // not a recoverable error
});
};
});
67. $httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
return promise.then(function(response) {
return response; // no action, was successful
}, function (response) {
// error - was it 401 or something else?
if (response.status===401 && response.data.error && response.data.error === "invalid_token") {
var deferred = $q.defer(); // defer until we can re-request a new token
// Get a new token... (cannot inject $http directly as will cause a circular ref)
$injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK')
.then(function(loginResponse) {
if (loginResponse.data) {
$rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope
// now let's retry the original request
$injector.get("$http")(response.config).then(function(response) {
// we have a successful response - resolve it using deferred
deferred.resolve(response);
},function(response) {
deferred.reject(); // something went wrong
});
} else {
deferred.reject(); // login.json didn't give us data
}
}, function(response) {
deferred.reject(); // token retry failed, redirect so user can login again
$location.path('/user/sign/in');
return;
});
return deferred.promise; // return the deferred promise
}
return $q.reject(response); // not a recoverable error
});
};
});
68. $httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
return promise.then(function(response) {
return response; // no action, was successful
}, function (response) {
// error - was it 401 or something else?
if (response.status===401 && response.data.error && response.data.error === "invalid_token") {
var deferred = $q.defer(); // defer until we can re-request a new token
// Get a new token... (cannot inject $http directly as will cause a circular ref)
$injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK')
.then(function(loginResponse) {
if (loginResponse.data) {
$rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope
// now let's retry the original request
$injector.get("$http")(response.config).then(function(response) {
// we have a successful response - resolve it using deferred
deferred.resolve(response);
},function(response) {
deferred.reject(); // something went wrong
});
} else {
deferred.reject(); // login.json didn't give us data
}
}, function(response) {
deferred.reject(); // token retry failed, redirect so user can login again
$location.path('/user/sign/in');
return;
});
return deferred.promise; // return the deferred promise
}
return $q.reject(response); // not a recoverable error
});
};
});
69. $httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
return promise.then(function(response) {
return response; // no action, was successful
}, function (response) {
// error - was it 401 or something else?
if (response.status===401 && response.data.error && response.data.error === "invalid_token") {
var deferred = $q.defer(); // defer until we can re-request a new token
// Get a new token... (cannot inject $http directly as will cause a circular ref)
$injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK')
.then(function(loginResponse) {
if (loginResponse.data) {
$rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope
// now let's retry the original request
$injector.get("$http")(response.config).then(function(response) {
// we have a successful response - resolve it using deferred
deferred.resolve(response);
},function(response) {
deferred.reject(); // something went wrong
});
} else {
deferred.reject(); // login.json didn't give us data
}
}, function(response) {
deferred.reject(); // token retry failed, redirect so user can login again
$location.path('/user/sign/in');
return;
});
return deferred.promise; // return the deferred promise
}
return $q.reject(response); // not a recoverable error
});
};
});
70. $httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
return promise.then(function(response) {
return response; // no action, was successful
}, function (response) {
// error - was it 401 or something else?
if (response.status===401 && response.data.error && response.data.error === "invalid_token") {
var deferred = $q.defer(); // defer until we can re-request a new token
// Get a new token... (cannot inject $http directly as will cause a circular ref)
$injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK')
.then(function(loginResponse) {
if (loginResponse.data) {
$rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope
// now let's retry the original request
$injector.get("$http")(response.config).then(function(response) {
// we have a successful response - resolve it using deferred
deferred.resolve(response);
},function(response) {
deferred.reject(); // something went wrong
});
} else {
deferred.reject(); // login.json didn't give us data
}
}, function(response) {
deferred.reject(); // token retry failed, redirect so user can login again
$location.path('/user/sign/in');
return;
});
return deferred.promise; // return the deferred promise
}
return $q.reject(response); // not a recoverable error
});
};
});
71. $httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
return promise.then(function(response) {
return response; // no action, was successful
}, function (response) {
// error - was it 401 or something else?
if (response.status===401 && response.data.error && response.data.error === "invalid_token") {
var deferred = $q.defer(); // defer until we can re-request a new token
// Get a new token... (cannot inject $http directly as will cause a circular ref)
$injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK')
.then(function(loginResponse) {
if (loginResponse.data) {
$rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope
// now let's retry the original request
$injector.get("$http")(response.config).then(function(response) {
// we have a successful response - resolve it using deferred
deferred.resolve(response);
},function(response) {
deferred.reject(); // something went wrong
});
} else {
deferred.reject(); // login.json didn't give us data
}
}, function(response) {
deferred.reject(); // token retry failed, redirect so user can login again
$location.path('/user/sign/in');
return;
});
return deferred.promise; // return the deferred promise
}
return $q.reject(response); // not a recoverable error
});
};
});
72. Environments
• Pretty usual to deal with prod, dev, testing
environment config on the server side
• Inject this into your client side app using a dynamic
JS include
Whole stack is JS, from the DB, the native data format (BSON), the server side and the front end. Even your DB queries are JSON and the DB client for debugging
Birmingham
EdTech learning platform
65 unis
1 million students 50% UK + other campuses on 3 continents
~15M hits per week page/api
Legacy in PHP
Node app here.
Angular app here
Note the node app serves index.html which includes app.js which bootstraps the angular app
Some things we are building with MEAN
Timeline editing UI
Textbooks
Copy and paste from OCRed images
Rich annotations + notes
In-book search
Bi directional is AWESOME!
Whistle stop tour
Bi directional is AWESOME!
Whistle stop tour
Pretty familiar
bind to params in URL
our HTML template
the controller
can add extra properties to the route
update() is a method in the CONTROLLER
Does this via event loop
dependancy injection
$scope and userSvc are singletons
SERVICES - generally logic for persisting models or other interactions with the server side
wrapping behavior and logic to specific tags, independent from view-level controller
independent scope, many on same view
great for re-use
user and textbook passed as parameters from view scope