6. Databases

Database

  • can handle large amount of data efficiently
  • provide tools to insert, query and update data
  • they offer security features
  • they can scale well
  • SQL: Structured Query Language, relational databases. Use tables. MySQL, Postgres, SQLite
  • NoSQL: store information in xml, yaml, json. No tables or schema to follow. MongoDB, Redis, Neo4j

MongoDB

  • mongo is commonly used with node and express
  • easy to start and poular
  • installation
  • multiple databases at the same time
  • create a new db use dbName (won’t appear until there is data in it)
  • BSON binary json, more compact in memory

MongoDB CRUD operations

Resource

Create

  • insert data in collections

    db.dogs.insertOne({name: "Charlie", age:3, breed:"corgie", isFriendly: true})

  • db.dogs.insert([{name: "Walt", breed: "Golden", age: 14}, {name: "Tonya", breed:"Chihua", age:17}])

  • show collections

  • db.dogs.find()

Find

  • db.dogs.find({breed: "corgi"})

Updating

  • db.dogs.updateOne({name: "Charlie"}, {$set: {age: 15}})

Deleting

  • db.dogs.deleteOne({name: "Charlie"})
  • db.dogs.deleteMany({})

Additional operators

  • for nested properties: db.dogs.find({'personality.childFriendly': true})
  • for multiple properties: db.dogs.find({'personality.childFriendly': true, age: 10})
  • comparison, logical operators etc db.dogs.find({ age: {$gt:20} })
  • in operator: the value of a field equals any value in the specified array db.dogs.find({breed: {$in ['Mutt', 'Corgi']}})
  • or db.dogs.find({$or: [{'personality.catfirendly': true}, {age: {$lte:2}}]})

Mongoose

Resource

  • Connect node to MongoDB -> driver or Mongoose
  • ODM: Object Data Mapper. Maps data from Mongo to JS objects
  • ORM: same but for SQL data

Create a new folder and initialize npm

npm init -y

Install mongoose

npm i mongoose

Make an index.js

touch index.js

Require mongoose

const mongoose = require('mongoose');

main().catch(err => console.log(err));

async function main() {
  //movieApp is the database
  await mongoose.connect('mongodb://localhost:17017/movieApp');
  console.log('connection open!')
} 

Make a model (class) to interact with the database.

  1. Define a schema
const productSchema = new mongoose.Schema({
    name: String,
    price: Number,
    quantity: Number
})
  1. make a model from the schema
//Product should be uppercase and singular. 
//mongoose will create a collection called movies
const Product = mongoose.model('Product', productSchema) 
  1. Create an instance of Movie
const amad = new Product({name: "Amad", price: 1986});
  1. Run node, load index.js from the project folder
node
.load index.js
  1. save it into mongodb

amad.save();

insert.Many()

Product.insertMany([
    {name: 'Amel', price:2001, quantity:8.3, rating:'R'},
    {name: 'The Iron', price:2004, quantity:3.3, rating:'R'},
])
.then(data => {
    console.log("it works")
    console.log(data)
})

No need to save into the db.

Finding

Can be treated as promises but they are not promises

 Product.find({}).then(data => console.log(data))
 Product.findById('123').then(data => console.log(data))

#### Updating

Product.updateOne({name:'Amad'},{price: 1984}).then( res => console.log(res)) does not return the object

Product.updateManu({name: {$in['Amd', 'sbyme']}}, {price:10}).then(res => console.log(res)) does not return the object

Product.findOneAndUpdate({name: 'tir'},{price: 7.0}).then(m => console.log(m)) we get back the (old) object

Product.findOneAndUpdate({name: 'tir'},{price: 7.0},{new: true}).then(m => console.log(m)) we get back the (new) object

Deleting

Product.remove({name: 'Amel'}).then(msg => console.log(msg))

Product.deleteMany({price: {$gte: 1999}}).then(msg => console.log(msg))

Product.findOneAndDelete(...) return the deleted object

Schemas

const productSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
        lowercase: true,
        trim: true,
        maxlength: 10
    },
    price: {
        type: Number,
        required: true,
        min: 0
    },
    onSale: {
        type: Boolean,
        default: false
    },
    qty: {
        online: {
            type: Number,
            default: 0
        },
        inStore: {
            type: Number,
            default: 0
        }
    }
})

Validation applies when data is created. When updating, it has to be set:

Product.findOneAndUpdate({name: 'Tyre'}, {price: 10},{new: true, runValidators: true})

Validation Errors

    price: {
        type: Number,
        required: true,
        min: [0, "Price must be positive"]
    },
    size:{
        type: String,
        enum: ['S','M','L'] //only these strings are allowed
    }

Model Instance

Do not use arrow function

productSchema.methods.toggleOnSale = function() {
    this.onSale = !this.onSale;
    return this.save();
}

productSChema.methods.addCategory = function(newCat){
    this.categories.push(newCat);
    return this.save();
}

const findProduct = async () => {
    const foundProduct = await Product.findOne({name: 'whatever'});
    await foundProduct.toggleOnSale();
    await foundProduct.addCategory('Outdoors');
}

Static Methods

productSchema.statics.fireSale = function(){
    return this.updateMany({},{onSale: true, price: 0})//return allows await
}
Product.fireSale().then(res => console.log(res))

Virtuals

Resource Add properties to schema that do not exist in the database but we can derive. i.e. full name in a database

const personSchema = new mongoose.Schema({
    first: String,
    last: String
})

personSchema.virtual('fullName').get(function () {
    return `${this.first} ${this.last}`
})
  set(function(v) {
    const firstName = v.substring(0, v.indexOf(' '));
    const lastName = v.substring(v.indexOf(' ') + 1);
    this.set({ firstName, lastName });
  });

Middleware

If youre move a user you can remove all his comments and activity:

.pre .post

presonSchema.pre('save', async function(){
  this.first = 'Yo';
  this.last = 'Meme';  
})

presonSchema.post('save', async function(){
    ...
})
Written on December 15, 2021