mongodb days uk: building apps with the mean stack

55

Upload: mongodb

Post on 15-Jan-2017

764 views

Category:

Technology


2 download

TRANSCRIPT

Page 1: MongoDB Days UK: Building Apps with the MEAN Stack
Page 2: MongoDB Days UK: Building Apps with the MEAN Stack

Building Apps w/ MEAN Stack

Page 3: MongoDB Days UK: Building Apps with the MEAN Stack

Get ready to be MEAN!

Page 4: MongoDB Days UK: Building Apps with the MEAN Stack

4

TopicsBackend ImplementationSchema DesignMEAN Stack BenefitsBest Practices

Page 5: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 6: MongoDB Days UK: Building Apps with the MEAN Stack

6

MEAN Stack

Page 7: MongoDB Days UK: Building Apps with the 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

Page 8: MongoDB Days UK: Building Apps with the MEAN Stack

Part 1: Let's Build a Shopping Cart

Page 9: MongoDB Days UK: Building Apps with the MEAN Stack

9

Search Products

Page 10: MongoDB Days UK: Building Apps with the MEAN Stack

10

Add Product to Shopping Cart

Page 11: MongoDB Days UK: Building Apps with the MEAN Stack

11

Check out Shopping Cart

Page 12: MongoDB Days UK: Building Apps with the MEAN Stack

App Structure

Page 13: MongoDB Days UK: Building Apps with the MEAN Stack

"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

Page 14: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 15: MongoDB Days UK: Building Apps with the MEAN Stack

17

Schema Relationships

Products CategoriesN 1

Users Shopping Cart1 N

Shopping Cart ProductsN N

Page 16: MongoDB Days UK: Building Apps with the MEAN Stack

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!

Page 17: MongoDB Days UK: Building Apps with the MEAN Stack

Part 2: Using the Mongoose ODM

Page 18: MongoDB Days UK: Building Apps with the MEAN Stack

20

Mongoose

Object Document Mapper Async Schema

Validation

http://mongoosejs.com/

Page 19: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 20: MongoDB Days UK: Building Apps with the MEAN Stack

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/

Page 21: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 22: MongoDB Days UK: Building Apps with the MEAN Stack

24

Takeaways

• Mongoose provides several neat features– MVC model– Default values – Schema validation– Declarative schema design

Page 23: MongoDB Days UK: Building Apps with the MEAN Stack

Part 3: Schema Design

Page 24: MongoDB Days UK: Building Apps with the MEAN Stack

26

Schema Design

• 3 schemas– Product– Category – User

• Define Mongoose schemas• Focus on a couple of key design principles

Page 25: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 26: MongoDB Days UK: Building Apps with the MEAN Stack

$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

Page 27: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 28: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 29: MongoDB Days UK: Building Apps with the MEAN Stack

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"]}

Page 30: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 31: MongoDB Days UK: Building Apps with the MEAN Stack

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!

Page 32: MongoDB Days UK: Building Apps with the MEAN Stack

34

Cardinality Matters

• Product and user = many-to-many relationship• User won't have 1000s of products in a cart

Page 33: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 34: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 35: MongoDB Days UK: Building Apps with the MEAN Stack

Part 4: Express.js

Page 36: MongoDB Days UK: Building Apps with the MEAN Stack

38

Express JS

Popular Node.js web framework

Simple, pluggable and fast

Great for build REST APIs

http://expressjs.com/

Page 37: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 38: MongoDB Days UK: Building Apps with the MEAN Stack

40

Feeling Awesome?

Page 39: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 40: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 41: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 42: MongoDB Days UK: Building Apps with the MEAN Stack

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 } }] }};

Page 43: MongoDB Days UK: Building Apps with the MEAN Stack

45

Takeaways

• Express REST API on top of Mongoose– Access control– Business logic– Decoupling database logic and access

Page 44: MongoDB Days UK: Building Apps with the MEAN Stack

Part 5: Angular.js

Page 45: MongoDB Days UK: Building Apps with the MEAN Stack

47

AngularJS

Front End Library

Extensible

Client Side MVC

https://angularjs.org/

Page 46: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 47: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 48: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 49: MongoDB Days UK: Building Apps with the MEAN Stack

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>

Page 50: MongoDB Days UK: Building Apps with the MEAN Stack

Bonus: Stripe checkout

Page 51: MongoDB Days UK: Building Apps with the MEAN Stack

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

Page 52: MongoDB Days UK: Building Apps with the MEAN Stack

Takeaways

Page 53: MongoDB Days UK: Building Apps with the MEAN Stack

55

MEAN Stack

• Flexible Stack • Features, features, features

– Lots of libraries available• Strong community support

ps – runs on the best database in the world!!!

Page 54: MongoDB Days UK: Building Apps with the MEAN Stack

56

Edx MEAN Stack Online Course

https://www.edx.org/course/introduction-mongodb-using-mean-stack-mongodbx-m101x

Page 55: MongoDB Days UK: Building Apps with the MEAN Stack

Obrigado!• Norberto Leite• Technical Evangelist• [email protected]• @nleite