Mohammad Umair Khan
Trainer, Mentor, Foo, Bar !
NodeJS, ExpressJS, MongoDB
Don't copy paste code
Mocha
LiveScript
10 Days
NodeJS, ExpressJS, MongoDB
const fs = require('fs');
const zlib = require('zlib');
const readStream = fs.createReadStream('./big.file');
readStream
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('./compressed.gz'));
Callstack > APIs > Callback Queue > Event loop > Callstack
NodeJS, ExpressJS, MongoDB
Addressable Resources. Every “thing” on your network should have an ID. With REST over HTTP, every object will have its own specific URI.
A Uniform, Constrained Interface. When applying REST over HTTP, stick to the methods provided by the protocol. This means following the meaning of GET, POST, PUT, and DELETE religiously.
Representation oriented. You interact with services using representations of that service. An object referenced by one URI can have different formats available. Different platforms need different formats. AJAX may need JSON. A Java application may need XML.
Communicate statelessly. Stateless applications are easier to scale.
{
"typ": "JWT",
"alg": "HS256"
}
{
"userId": "090078601",
"roles": ["Admin", "King"]
}
// signature algorithm
data = `${base64urlEncode(header)}.${base64urlEncode(payload)}`
signature = Hash(data, secret);
header.payload.signature
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
NodeJS, ExpressJS, MongoDB
{
"_id": ObjectID("1298798e198291"),
"username": "bruce@wayne.com",
"password": "kasjdl123k1092sad123wdadakjhdka12o31",
"classes": [{
"name": "Physics",
"classId": "PHY-101"
},
{
"name": "Chemistry",
"classId": "CHM-101"
}
]
}
[
{
"_id": ObjectID("1298798e198291"),
"username": "bruce@wayne.com",
"password": "kasjdl123k1092sad123wdadakjhdka12o31"
},
{
"_id": ObjectID("1298asd1238sad"),
"username": "robin@wayne.com",
"password": "aslkdjpo2eposdopo122weopqsdioqwe1231"
}
]
[
{
"_id": ObjectID("1298798e198291"),
"username": "bruce@wayne.com",
"password": "kasjdl123k1092sad123wdadakjhdka12o31",
"accounts": [
{ "type": "openid"},
{ "type": "oauth2" }
]
},
{
"_id": ObjectID("1298asd1238sad"),
"username": "robin@wayne.com",
"password": "aslkdjpo2eposdopo122weopqsdioqwe1231"
}
]
src: BSON Types
// person
{
name: "Keon Kim",
hometown: "Seoul",
addresses: [
{ city: 'Manhattan', state: 'NY', cc: 'USA' },
{ city: 'Jersey City', state: 'NJ', cc: 'USA' }
]
}
All information in one query
It is impossible to search the contained entity (addresses) independently
{
_id: ObjectID('AAAA'),
partno: '123-aff-456',
name: 'Awesometel 100Ghz CPU',
qty: 102,
cost: 1.21,
price: 3.99
}
// products
{
name: 'Weird Computer WC-3020',
manufacturer: 'Haruair Eng.',
catalog_number: 1234,
parts: [
ObjectID('AAAA'),
ObjectID('DEFO'),
ObjectID('EJFW')
]
}
It is easy to handle insert, delete on each documents independently
It has flexibility for implementing N-to-N relationship because it is an application level join
Performance drops as you call documents multiple times.
> product = db.products
.findOne({catalog_number: 1234});
db.parts.find({
_id: { $in : product.parts }}
).toArray() ;
// host
{
_id : ObjectID('AAAB'),
name : 'goofy.example.com',
ipaddr : '127.66.66.66'
}
// logmsg
{
time : ISODate("2015-09-02T09:10:09.032Z"),
message : 'cpu is on fire!',
host: ObjectID('AAAB')
}
Scales up with the 16MB per document limit
> host = db.hosts
.findOne({ipaddr : '127.66.66.66'});
> db.logmsg.find({host: host._id})
.sort({time : -1})
.limit(5000)
.toArray()
// person
{
_id: ObjectID("AAF1"),
name: "Koala",
// reference task document
tasks [
ObjectID("ADF9"),
ObjectID("AE02"),
ObjectID("AE73")
]
}
// tasks
{
_id: ObjectID("ADF9"),
description: "Practice Jiu-jitsu",
due_date: ISODate("2015-10-01"),
// reference person document
owner: ObjectID("AAF1")
}
It is easy to search on both Person and Task documents
It requires two separate queries to update an item. The update is not atomic.
// products - before
{
name: 'Weird Computer WC-3020',
manufacturer: 'Haruair Eng.',
catalog_number: 1234,
parts: [
ObjectID('AAAA'),
ObjectID('DEFO'),
ObjectID('EJFW')
]
}
// products - after
{
name: 'Weird Computer WC-3020',
manufacturer: 'Haruair Eng.',
catalog_number: 1234,
parts: [ // denormalization
{ id: ObjectID('AAAA'), name: 'Awesometel 100Ghz CPU' },
{ id: ObjectID('DEFO'), name: 'AwesomeSize 100TB SSD' },
{ id: ObjectID('EJFW'), name: 'Magical Mouse' }
]
}
It is not a good choice when updates are frequent
When you want to update the part name, you have to update all names contained inside product document
Denormalization reduces the cost of calling the data.
// parts - before
{
_id: ObjectID('AAAA'),
partno: '123-aff-456',
name: 'Awesometel 100Ghz CPU',
qty: 102,
cost: 1.21,
price: 3.99
}
// parts - after
{
_id: ObjectID('AAAA'),
partno: '123-aff-456',
name: 'Awesometel 100Ghz CPU',
// denormalization
product_name: 'Weird Computer WC-3020',
// denormalization
product_catalog_number: 1234,
qty: 102,
cost: 1.21,
price: 3.99
}
It is not a good choice when updates are frequent
When you want to update the part name, you have to update all names contained inside product document
Denormalization reduces the cost of calling the data.
// logmsg - before
{
time : ISODate("2015-09-02T09:10:09.032Z"),
message : 'cpu is on fire!',
host: ObjectID('AAAB')
}
// logmsg - after
{
time : ISODate("2015-09-02T09:10:09.032Z"),
message : 'cpu is on fire!',
host: ObjectID('AAAB'),
ipaddr : '127.66.66.66'
}
> db.logmsg.find({ipaddr : '127.66.66.66'})
.sort({time : -1})
.limit(5000)
.toArray()
{
time : ISODate("2015-09-02T09:10:09.032Z"),
message : 'cpu is on fire!',
ipaddr : '127.66.66.66',
hostname : 'goofy.example.com'
}
array[200+] == Don't embed
array[2000+] == Don't use ObjectID references
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
"address" : {
"building" : "2911",
"coord" : [
-73.982241,
40.576366
],
"street" : "West 15 Street",
"zipcode" : "11224"
},
"borough" : "Brooklyn",
"cuisine" : "Italian",
"grades" : [
{
"date" : ISODate("2014-12-18T00:00:00Z"),
"grade" : "A",
"score" : 13
},
{
"date" : ISODate("2014-05-15T00:00:00Z"),
"grade" : "A",
"score" : 12
},
{
"date" : ISODate("2013-06-12T00:00:00Z"),
"grade" : "A",
"score" : 9
}
],
"name" : "Gargiulo's Restaurant",
"restaurant_id" : "40365784"
}
db.collection.aggregation([
{_stage1_},
{_stage2_}
]);
Untangles the array by creating multiple records for each item in the array
db.restaurants.aggregate([
{$unwind: "$grades"}
])
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
"address" : {
"building" : "2911",
"coord" : [-73.982241, 40.576366],
"street" : "West 15 Street",
"zipcode" : "11224"
},
"borough" : "Brooklyn",
"cuisine" : "Italian",
"grades" : [{
"date" : ISODate("2014-12-18T00:00:00Z"),
"grade" : "A",
"score" : 13
},
{
"date" : ISODate("2014-05-15T00:00:00Z"),
"grade" : "A",
"score" : 12
},
{
"date" : ISODate("2013-06-12T00:00:00Z"),
"grade" : "A",
"score" : 9
}],
"name" : "Gargiulo's Restaurant",
"restaurant_id" : "40365784"
}
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
...
"grades" : {
"date" : ISODate("2014-12-18T00:00:00Z"),
"grade" : "A",
"score" : 13
},
...
},
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
...
"grades" : {
"date" : ISODate("2014-05-15T00:00:00Z"),
"grade" : "A",
"score" : 12
},
...
}
Returns the records with the matching condition
db.restaurants.aggregate([
{$unwind: "$grades"},
{$match: {"grades.grade": "A"}
])
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
...
"grades" : {
"date" : ISODate("2014-12-18T00:00:00Z"),
"grade" : "B",
"score" : 13
},
...
},
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
...
"grades" : {
"date" : ISODate("2014-05-15T00:00:00Z"),
"grade" : "A",
"score" : 12
},
...
}
...
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
...
"grades" : {
"date" : ISODate("2014-05-15T00:00:00Z"),
"grade" : "A",
"score" : 12
},
...
}
...
Adds a custom field in each record
db.restaurants.aggregate([
{$unwind: "$grades"},
{$match: {"grades.grade": "A"}
{$addFields: {grade: "$grades.grade"}}
])
...
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
...
"grades" : {
"date" : ISODate("2014-05-15T00:00:00Z"),
"grade" : "A",
"score" : 12
},
...
}
...
...
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
...
"grades" : {
"date" : ISODate("2014-05-15T00:00:00Z"),
"grade" : "A",
"score" : 12
},
"grade": "A"
...
}
...
Create a projection
db.restaurants.aggregate([
{$unwind: "$grades"},
{$match: {"grades.grade": "A"}
{$addFields: {grade: "$grades.grade"}},
{$project: {_id:1, grade: 1, name: 1}}
])
...
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
...
"grades" : {
"date" : ISODate("2014-05-15T00:00:00Z"),
"grade" : "A",
"score" : 12
},
"grade": "A"
...
},
{
"_id" : ObjectId("59074c7c057a12312a12da64"),
...
"grades" : {
"date" : ISODate("2014-05-15T00:00:00Z"),
"grade" : "A",
"score" : 33
},
"grade": "A"
...
}
...
...
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
"grade": "A",
"name": "Ponche Taqueria"
},
{
"_id" : ObjectId("59074c7c057a12312a12da64"),
"grade": "A",
"name": "Fordham Pizza & Pasta"
}
...
Create a projection
db.restaurants.aggregate([
{$unwind: "$grades"},
{$match: {"grades.grade": "A"}
{$addFields: {grade: "$grades.grade"}},
{$project: {_id:1, grade: 1, name: 1}},
{$group: {_id: "$name", count: {$sum: 1}}}
])
...
{
"_id" : ObjectId("59074c7c057aaffaafb0da64"),
"grade": "A"
},
{
"_id" : ObjectId("59074c7c057a12312a12da64"),
"grade": "A"
}
...
{ "_id" : "Ponche Taqueria", "count" : 3 }
{ "_id" : "Place To Beach Cantina", "count" : 1 }
{ "_id" : "Homecoming", "count" : 3 }
{ "_id" : "Fordham Pizza & Pasta", "count" : 3 }
db.restaurants.aggregate([
{$unwind: "$grades"},
{$match: {"grades.grade": "A"}
{$addFields: {grade: "$grades.grade"}},
{$project: {_id:1, grade: 1, name: 1}},
{$group: {_id: "$name", count: {$sum: 1}}}
])
Builds an in memory tree so that specific searches do not require an entire collection scan
use reporting
db.createUser(
{
user: "reportsUser",
pwd: "12345678",
roles: [
{ role: "read", db: "reporting" },
{ role: "read", db: "products" },
{ role: "read", db: "sales" },
{ role: "readWrite", db: "accounts" }
]
}
)
By Mohammad Umair Khan