Date post: | 15-Jan-2017 |
Category: |
Technology |
Upload: | mongodb |
View: | 765 times |
Download: | 2 times |
Building Apps w/ MEAN Stack
Get ready to be MEAN!
4
TopicsBackend ImplementationSchema DesignMEAN Stack BenefitsBest Practices
5
Building and MEAN app
• MongoDB is great for storing web/mobile app data
• So let’s build a REST API using Node.js!– learn a bit about overall MEAN Stack libraries– learn a few MongoDB schema design principles
6
MEAN Stack
7
Overview
• Part 1: Shopping Cart Application– Search for products– Add them to your cart– Check out with Stripe
• Part 2: Using the Mongoose ODM• Part 3: Schema Design• Part 4: Building an API with the Express framework• Part 5: Front End w/ Angular
Part 1: Let's Build a Shopping Cart
9
Search Products
10
Add Product to Shopping Cart
11
Check out Shopping Cart
App Structure
"Bad programmers worry about the code. Good programmers worry about data structures and their relationships." - Linus Torvalds
3 schemas for 3 collections
Products Categories Users
16
Schema Relationships
Entity A Entity B1 1
Document A
Document B
Entity A Entity B1 N
Document A
Array of B's
Entity A Entity B1 NNNN
Entity A Entity BN N
Document A Document B
17
Schema Relationships
Products CategoriesN 1
Users Shopping Cart1 N
Shopping Cart ProductsN N
18
Schema Relationships
• Product belongs to one or more categories• Users can have multiple products in their cart• Representing relationships in MongoDB is tricky
( different from doing a traditional RDBMS system – based on the usage patterns of data)
• But that’s what mongoose HELPS A LOT!
Part 2: Using the Mongoose ODM
20
Mongoose
Object Document Mapper Async Schema
Validation
http://mongoosejs.com/
21
Your first Mongoose Schema!var mongoose = require('mongoose');
module.exports = new mongoose.Schema( { name:{ type: String, required: true, } email:{ type: String, required: true, match: /.+@.+\..+/, lowercase: true }, loggedInCounter:{ type: Number, default: 0, } });
"require" mongoose
Define fields
Field types, validation rules and attributes
Document Validation
Available on 3.2.0-RC2https://jira.mongodb.org/browse/SERVER-18227http://www.eliothorowitz.com/blog/2015/09/11/document-validation-and-what-dynamic-schema-means/
23
Using your schemavar mongoose = require('mongoose');var schema = require('./schemas/user');
mongoose.connect('mongodb://localhost:27017/meancart');
var User = mongoose.Model('User', schema, 'users');
var newuser = new User({ name: 'Jose Mourinho', email: '[email protected]',});
newuser.save( function(error){ if (error){ console.log(error); process.exit(1); } User.find({email: '[email protected]'}, function(error, docs){ if(error){ console.log(error); process.exit(1); } console.log(require('util').inspect(docs)); process.exit(1); });} );
Tell mongoose where to connect
Create model using given schema and initialize object
Save user and load it from MongoDB
24
Takeaways
• Mongoose provides several neat features– MVC model– Default values – Schema validation– Declarative schema design
Part 3: Schema Design
26
Schema Design
• 3 schemas– Product– Category – User
• Define Mongoose schemas• Focus on a couple of key design principles
27
Category Schemavar mongoose = require('mongoose');
var categorySchema = { _id: { type: String}, parent: { type: String, ref: 'Category', }, ancestors: [{ type: String, ref: 'Category' }]};
module.exports = new mongoose.Schema(categorySchema);module.exports.categorySchema = categorySchema;
Inner sub-document array
Self "join" reference
http://mongoosejs.com/docs/populate.html
$lookup
Go and try it out w/ 3.2.0-RC2:https://jira.mongodb.org/browse/SERVER-19095https://www.mongodb.com/blog/post/thoughts-on-new-feature-lookup
29
Product Schemavar mongoose = require('mongoose');var Category = require('./category');
var productSchema = { name: { type: String, required: true }, pictures: [{ type: String, match: /^http:\/\//i}], price: { amount: { type: Number, required: true}, currency: { type: String, enum: ['USD', 'EUR', 'GBP'], required: true }, }, category: Category.categorySchema};
module.exports = new mongoose.Schema(productSchema);module.exports.productSchema = productSchema;
Category Schema
30
Creating a Productvar mongoose = require('mongoose');var productSchema = require('./product');
var Product = mongoose.model('Product', productSchema);
var p = new Product({ name: 'chelsea scarf blue', price: { amount: 12.97, currency: 'GBP', }, category:{ name:'scarfs' }});
p.name = 2;console.log(p.name); //2console.log(p.$isValid('name')); // true
p.price.amount = 'Not a number';console.log(p.$isValid('price.amount')); //false
p.validate(function(err){ // CastError because `price.amount` couldn't be // casted to a number console.log(err);});
Cast Validation
Invalid Cast
Validation Method
31
Category Schema Queries• What categories are descent of "wearables" ?
• What categories are children of "accessories"?
• What categories are ancestors of "scarf"?
db.category.find( {'ancestors': 'wearables'}){"_id": "wearables", "parent": "accessories", "ancestors": ["accessories","wearables"]}{"_id": "scarfs", "parent": "wearables", "ancestors": ["accessories", "wearables", "scarfs"]}
db.category.find( {'parent': "accessories"}){"_id": "wearables", "parent": "accessories", "ancestors": ["accessories","wearables"]}
db.category.find( {'_id': 'scarf'}, {"_id":0, "ancestors":1}){"ancestors": ["accessories","wearables"]}
32
… make sure that:
• Queries should be simple!• Strive for minimal data transformation by server
– Store what you query for – How you use data defines your schema
• Aggregation Framework can complement complex queries but – are heavy– required resources
33
User Schemavar mongoose = require('mongoose');
var userSchema = { profile: { username: { type: String, required: true; lowercase: true; }, picture:{ type: String, required: true, match: /^http:\/\//i, }, }, data:{ cart:[{ product: { type: mongoose.Schema.Types.ObjectId }, quantity:{ type: Number, defalt: 1, min: 1 } }] }};module.exports = new mongoose.Schema(userSchema);module.exports.userSchema = userSchema;
Embedded Cart Entity
Watch out for unbounded arrays!
34
Cardinality Matters
• Product and user = many-to-many relationship• User won't have 1000s of products in a cart
35
Cardinality Matters
• Product and user = many-to-many relationship• User won't have 1000s of products in a cart• Embedded array to represent that relationship
Shopping Cart ProductsN N
36
Linking vs. Embedding
• Embedding–Great for read performance–Heavier writes–Great for many-to-one when many is known!
• Linking–Allows more Flexibility–Extra work on read workloads–Simpler writes
Part 4: Express.js
38
Express JS
Popular Node.js web framework
Simple, pluggable and fast
Great for build REST APIs
http://expressjs.com/
Your First Express Appvar express = require('express');var app = express();app.get('/products', function(req, res){ var limit = 10; Product.find().limit(10).populate('category'). exec( function(err, docs){ if(err){ console.log('something is wrong: '+err.toString()); return res.status(status.INTERNAL_SERVER_ERROR). json({error: err.toString()}); } res.json({products: docs}); });});//start listeningapp.listen(3000);console.log("super REST app running");
Initiate app objects
Define route and method
Use Model to execute database calls
40
Feeling Awesome?
41
Structure your REST API
var bodyParser = require('body-parser');var express = require('express');
var app = express();
//body parser for json payloadsapp.use(bodyParser.json());
//api versionapp.use('/api/v1', require('./api');
//start listeningapp.listen(3000);console.log("super REST app running");
Define a separate module for your version implementation
Structure your REST APIvar express = require('express');var status = require('http-status');var mongoose = require('mongoose');var productSchema = require('./schemas/product');
mongoose.connect('mongodb://localhost/test');var Product = mongoose.model('Product', productSchema);
var api = express.Router();
api.get('/products', function(req, res){ var limit = 10; var items = Product.find().limit(10). populate('category'). exec( function(err, docs){ if(err){ console.log('something went wrong: '+err.toString()); return res.status(status.INTERNAL_SERVER_ERROR). json({error: err.toString()}); } res.json({products: docs}); });
});
module.exports = api;
Express Router object
Define route and method
43
GET /category/parent/:idfunction errorHandler(res, err){ console.log('something went wrong: '+err.toString()); return res.status(status.INTERNAL_SERVER_ERROR). json({error: err.toString()});}
api.get('/products', function(req, res){ ... });
api.get('/category/parent/:id'), function(req, res){ var query = { parent: req.params.id}; var sort = {_id: -1}; Category.find(query).sort(sort).exec(function(err, docs){ if (err){ return errorHandler(res, err); } res.json({categories: docs}); });});
Define route
Handle params and create queries
44
Adding Products to User's Cart
api.put('/me/cart', function(req, res){ try{ var cart = req.body.cart; }catch(e){ return errorHandler(res, error); } req.user.data.cart = cart; req.user.save(function(err, user){ if(err){ return errorHandler(res, err); } return res.json({user: user}); });});
Overwrite user's cart
Let mongoose handle validation and casting of data
Mongoose lets you be lazyAccess control using subdocuments
var userSchema = { profile: {...}, data:{ oauth: {type:String, required: true}, cart:[{ product: { type: mongoose.Schema.Types.ObjectId }, quantity:{type: Number, defalt: 1,min: 1 } }] }};
45
Takeaways
• Express REST API on top of Mongoose– Access control– Business logic– Decoupling database logic and access
Part 5: Angular.js
47
AngularJS
Front End Library
Extensible
Client Side MVC
https://angularjs.org/
Front End Servervar express = require('express');var app = express();app.use(express.static('front'));
app.get('/', function(req, res){ //load html page and let angular do all the wiring res.sendfile('./front/index.html');});
var server = app.listen(8080, function () { var host = server.address().address; var port = server.address().port;
console.log('Front end listening at http://%s:%s', host, port);});
Using expressjs
Self "join" reference
index.html<!doctype html><html lang="en" ng-app="meancart" ng-controller="listController"><head> <meta charset="utf-8"> <title>{{title}}</title>
<link rel="stylesheet" href="css/bootstrap.min.css"> <script src="js/angular.min.js"></script> <script src="js/controllers.js"></script></head>
App and Controller
Angular Controllersconst RESTSERVER = "http://localhost:3000";
var app = angular.module('meancart', []);app.controller('listController', function($scope, $http){ $http.get(RESTSERVER+"/api/v1/products").success( function( response ){ $scope.title= "MEAN Cart!"; $scope.name= "List of Products"; $scope.list = response._items;
});} );
Backend REST server call
Templates <!--index.html--> <div class="col-md-10">
<div ng-include="'/templates/list.html'"></div> </div>
<!--list.html--><br><ul class="media-list"> <li ng-repeat="item in list|filter:query" class="media"> <div ng-include="'templates/item.html'"></div> </li></ul>
<!--item.html<a class="pull-left" href="{{item.url}}"> <img class="img-responsive" src="{{item.img}}"></a><div class="media-body"> <h4 class="media-heading">{{item.name}}</h4></div>
Bonus: Stripe checkout
Checkout w/ StripeStripe = require('stripe');api.post('/checkout', function(req, res){ if(!req.user){...}
req.user.populate( {path: 'data.cart.product', model:'Product'}, function(err, user){ var totalCostGBP = 0; _.each(user.data.cart, function(item){ totalCostGBP += item.product.amount * item.quantity; }); Stripe.charges.create({ // Stripe requires the price in cents amount: Math.ceil(totalCostGBP*100), currency: 'gbp', source: req.body.stripeToken, description: 'Thank you for your money' }, function(err, charge){...} ); });});
Populate the user object
Import Stripe module and create a charge
Takeaways
55
MEAN Stack
• Flexible Stack • Features, features, features
– Lots of libraries available• Strong community support
ps – runs on the best database in the world!!!
56
Edx MEAN Stack Online Course
https://www.edx.org/course/introduction-mongodb-using-mean-stack-mongodbx-m101x
Obrigado!• Norberto Leite• Technical Evangelist• [email protected]• @nleite