mongodb days uk: building apps with the mean stack

Post on 15-Jan-2017

762 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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: 'specialone@chelseafc.co.uk',});

newuser.save( function(error){ if (error){ console.log(error); process.exit(1); } User.find({email: 'specialone@chelseafc.co.uk'}, 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• norberto@mongodb.com• @nleite

top related