MongoDB Flashcards

1
Q

What is MongoDB?

A

MongoDb is a NoSQL database.
A mongo database is built of collections (which translate to SQL tables), and documents (which translate to Rows).

Features:

  • Document based (Mongo stores data in documents (field-value pair data structures))
  • Scalable (Very easy o distribute data across multiple macheines as the users grow)
  • Flexible (No document data schema require, so each document can have different number and type of fields)
  • Performant (thanks to things like Embedded data models, indexing, sharding, flexible documents, native duplication)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

What is BSON?

A

Bin­ary JSON, is a bin­ary-en­coded seri­al­iz­a­tion of JSON-like doc­u­ments. Like JSON, BSON just like JSON sup­ports the em­bed­ding of doc­u­ments and ar­rays with­in oth­er doc­u­ments and ar­rays.
The real difference is that BSON values are typed.

The max size of a BSON document is 16mb

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

What is Embedding/Denormalizing?

A

It is a process of including related data into a single document. This allows for quicker access and easier data models (however it’s not always the best solution).

The opposite of embedding is normalizing -> In relational databases, this is how the data is always modelled.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

How do you checkout/create a DB in mongo shell?

A

use dbName

if the db is existing - it will switch to the selected database, otherwise it will create a new database.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

How do you insert documents to a collection?

A

db. .insertMany() // and we pass a JS object - it will convert it into JSON and BSON.
db. .insertOne()

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

How do you view, documents/ collections databases in mongodb shell

A

db.tours.find()
show dbs
show collections

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

How do you query for exact match in MongoDB?

A

You query the collections, by db.tours.find and passing a filter object like:
db.tours.find({difficulty: “easy”})

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

How do you query with operators in MongoDB?

A

We pass a mongo operator nested as the object for the values we are searching for.

db.tours.find({price: {$lte: 500}})

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

How do you add AND to a query?

A

You just simply pass the key, as another parameter in the filter query. Like:
db.tours.find{price: {$lte: 500}, rating: {$gt: 4.7} }

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

How do you add an OR operator in MongoDB?

A

db.tours.find({$or: [{difficulty: “easy”}, {rating: {$lte: 4.8}}]})

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What is a Projection?

A

By default, queries in MongoDB return all fields in matching documents. To limit the amount of data that MongoDB sends to applications, you can include a projection document to specify or restrict fields to return.
Projection simply means, that we want to select some fields in the output.

In order to add projection to the query, we need to pass another object in the query
db.tours.find({$or: [{difficulty: “easy”}, {rating: {$lte: 4.8}}]}, {name: 1})
Result, will be only names of the matching documents

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

How do you update documents?

A

db.tours.updateOne({name: “The Snow Adventurer”, {$set: {price: 597} }})

updateOne, even if it matches multiple documents, will update just one. If we want to update many documents, we should use updateMany function.

updateMany and updateOne, will update the fields that we specify in the $set operator, we can however switch the whole document we could use replaceOne or replaceMany functions
(similar to PUT, PATCH).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Deleting documents in MongoDB

A

db.tours.deleteMany({ rating: {$lt: 4.8}})

we can deleteOne or deleteMany documents, as a parameter we need to specify the filter object.

If we want to delete all documents in a collection, we need to pass an empty object.

db.tours.deleteMany({})
(the empty object is basically a condition that all the documents always match).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

How do you connect to a MongoDB with Express app?

A

In server.js, which is the main file for starting the app, we have references to
app.js (express app and middlewares),
environment variables (dotenv package)
and mongoose()

we need to mongoose.connect(connection_string, { useNewUrlParser: true, useCreateIndex: true, useFindAndModify: false });
and as an argument we pass a connection string and an options object.

IF we are connecting through mongoDB Atlas, we will be provided with that query string.
If we are hosting the DB locally, we will need to build the connection string like: mongodb://localhost:/27017/db_name

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Saving to mongoDB using Mongoose

A

In order to save to mongodb, we need to
1. const mongoose= require(‘mongoose’);
2. const Store = mongoose.model(‘Store’) // import the store schema, that we defined eariler.
3. In the middleware that handles POST request
a. Instantiate the schema
const store = new Store(req.body)
store.save();

we can also store the response object that we get when we await the save, by wrapping the function and chaining the save() method.

const store = await (new Store(req.body)).save()

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

What is mongoose?

A

Mongoose is an Object Data Modeling (ODM) Library for MongoDB and Node.js, providing a higher level of abstraction.

  • schemas to model data and relationshipts
  • easy data validation
  • simple query api
  • middleware

Mongoose schema: where we model our data by describing the structure of the data, default values, and validation.
Mongoose model: a wrapper for the schema, providing an interface to the database for CRUD operations.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

How do you set up a strict Schema in mongo db with Mongoose?

A
In order to set up a strict schema, in our models folder we should create a schema file.
const mongoose = require('mongoose')
const mongoose.Promise  = global.Promise;
set up a schema by instantiating a new mongoose.Schema, As an argument, we pass a schema object, where we specify fields and type of the data
const tourSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'A tour must have a name'],
    unique: true
  },
  rating: {
    type: Number,
    default: 4.5
  },
  price: {
    type: Number,
    required: [true, 'A tour must have a price']
  },
  summary: {
    type: String,
    trim: true
  },
});

module.exports = mongoose.model(‘Store’, storeSchema);

Then this model needs to be imported on app startup.
require(‘./models/Store)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

How do you create documents using Mongoose?

A

The best solution is to create an async function that will be located in the controllers folder.
Based on the Schema we can create an object in the database.

const newTour = await Tour.create(req.body);

res.status(201).json({
status: ‘success’
data: {
tour: newTour
}
}

In order to handle errors, as usual with async/await funcitons, we should wrap the functions with try catch block (or create a catchAsync function)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

How do you read documents with Mongoose?

A

In order to read documents from the MongoDB database, we need to use
Tour.find() function, just like in MongoDB query.
We can of course pass a query string as an argument of the function and that will filter out the results.

There are many helper mongoose methods, that help us to findById, or findByIdAndUpdate, if we will be using another operations.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

How do you update documents with mongoose

A

You update a single document using findByIdAndUpdate(id, obj, {});
In the arguments, you pass the id, that you would like to query, then the data that we want to change, and the optiosn object.
- In the options object we can define that the returned document, will include the updated info, by adding new:true, to the options object,
- We can also define if we want to runValidators: true

const tour = await     const tour = await Tour.findByIdAndUpdate(req.params.id, req.body, {
      new: true,
      runValidators: true
    });
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

Deleting documents in mongoose

A

In order to delete a document,
we need to await Tour.findByIdAndDelete(req.params.id)
and handle success and erros with try and catch blocks.

In REST API it is common practice to return nothing in the DELETE operation.

res.status(204).json({
  status: 'success',
  data: null,
});
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

What are the most popular Schema types options?

A

required: boolean or function, if true adds a required validator for this property
default: Any or function, sets a default value for the path. If the value is a function, the return value of the function is used as the default.
unique: boolean, whether to define a unique index on this property.
select: boolean, specifies default projections for queries
validate: function, adds a validator function for this property

STRING
trim: boolean, whether to always call .trim() on the value

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

What is the standard way of writing a query string including gte, or lte operators?

A

In the query string we need to add another part to the key-value pair.

The standard way is to include the operators in the square brackets.
duration[gte]=5

The object that we receive in req.query is almost identically mongoDB filter object, the only thing it is missing is the $ mongo db operator.

localhost:3000/api/v1/tours?duration[gte]=5&price[gt]=1500

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

How do you sort incoming data in mongoose?

A

we can sort when we run
.sort() function on the query object that we receive from mongoose.

If we want to deal with ties, we need to give a string of properties joined with a space as an argument.

If we want to sort by descending order, we need to add ‘-‘ before the property.

localhost:3000/api/v1/tours?sort=-price,-ratingsAverage

25
Q

How do we limit fields in mongoose?

A

In order to limit the fields that are incoming from the API, we need to run
.select() function on the query object.
As argument we pass a string of fields joined by space

The mongoose engine, will then run projection on the query and output only selected fields in the query string

If we want to EXCLUDE fields, we prefix them with a ‘-‘

localhost:3000/api/v1/tours?fields=name,duration,difficulty

We can also exclude fields right from the schema (sensitive data like passwords)
we need to add a
select: false
in the options object.

26
Q

How do you implement pagination with mongoose?

A

In mongoose, there are 2 important methods that can be invoked on the Query Object.

.skip() - is amount of the documents that we want to skip.
.limit() - is like limit in mongoDB

So in order to implement a ?page=1&limit10 option on querystring, we need to create a formula for skip
skip = (page - 1) * limit

We should also handle errors if skip >= number of tours.
In order to count that we need to await Tour.countDocuments()

27
Q

What is mongodb Aggregation Pipeline

A

Aggregation pipeline is power mongoDB feature that allows us to group the results by specific fields and display them.

$match() stage – filters those documents we need to work with, those that fit our needs
$group() stage – does the aggregation job
$sort() stage – sorts the resulting documents the way we require (ascending or descending)
The input of the pipeline can be one or several collections.

The pipeline then performs successive transformations on the data until our goal is achieved.

28
Q

How do you implement aggregation pipeline in mongoose?

A

In a specific route, we need to implement an async function that will return a request

The method, that will prepare the stats object is
Tour.aggregate([])
In the aggregate argument we pass a pipeline Array, that will execute the transformations in sequence it is in the array.

    const stats = await Tour.aggregate([
      {
        $match: { ratingsAverage: { $gte: 4.5 } },
      },
      {
        $group: {
          _id: { $toUpper: '$difficulty' },
          numTours: { $sum: 1 },
          numRatings: { $sum: '$ratingsQuantity' },
          avgRating: { $avg: '$ratingsAverage' },
          avgPrice: { $avg: '$price' },
          minPrice: { $min: '$price' },
          maxPrice: { $max: '$price' },
        },
      },
      {
        $sort: {
          avgPrice: 1,
        },
      },
    ]);
29
Q

What does $unwind stage do in the aggregation pipeline?

A

Unwind will deconstruct an array field from the input documents and then output one document for each element of the array.

30
Q

How do you define virtual properties on a Schema in mongoose?

A

In order to define a virtual property we need to:

1) define a getter for the property
tourSchema.virtual('durationWeeks').get(function (){
  return this.duration / 7
})
2) In the schema object, pass another argument (optionsObj), where we define
  {
    toJSON: {
      virtuals: true,
    },
    toObject: {
      virtuals: true,
    },
  }
31
Q

What is document middleware in mongoose?

A

Just like in express, we can use mongoose middleware to make something happen between two events. For instance before or after ‘save’ event. We defined this middleware on the schema.

Document middleware is a middleware that can act on currently processed document. Hence this keyword, refers to the document.

Schema.pre('save', function(next){
  // do something
  next();
});
Schema.post('save', function (doc, next){
  // do something
  next();
});

These document middleware are called hooks. (Presave hook)

Types of hooks:
validate
save
remove
updateOne
deleteOne
init (note: init hooks are synchronous)
32
Q

What are query middlewares in mongoose?

A

Query middlewares are the queries that point to the query that is being processed. this inside of the query middleware refers to the query.

// Implementation for all hooks beginning with find.
tourSchema.pre(/^find/g, function (next) {
  // Secret tours will not be found
  this.find({ secretTour: { $ne: true } });
  next();
});
count
deleteMany
deleteOne
find
findOne
findOneAndDelete
findOneAndRemove
findOneAndUpdate
remove
update
updateOne
updateMany
33
Q

What is aggregation middleware in mongoose?

A

Aggregate middleware is for MyModel.aggregate(). Aggregate middleware executes when you call exec() on an aggregate object. In aggregate middleware, this refers to the aggregation object.

this.pipeline() object is an Array, that we defined in the .aggregate() function.
In order to add stages to the pipeline, we need to add a match stage to the begining of the aggregation pipeline:

  this.pipeline().unshift({
    $match: {
      secretTour: { $ne: true },
    },
  });
34
Q

What is the difference between Validation and Sanitization?

A

Validation is checking if entered value are in the right format for each field in the document schema, and that values have been entered for all of the required fields.

Sanitization is to ensure that the input data is “clean”. Meaning no malicious code is being injected into the application or the database. In that step we remove unwanted characters, or even code from input data

35
Q

What is mongoose built in data validation that we can put on the Schema (MODEL)

A

ALL DATA TYPES:
required

STRING:

minlength: Number, creates a validator that checks if the value length is not less than the given number
maxlength: Number, creates a validator that checks if the value length is not greater than the given number
match: RegExp, creates a validator that checks if the value matches the given regular expression
enum: Array, creates a validator that checks if the value is in the given array.

NUMBER:

min: Number, creates a validator that checks if the value is greater than or equal to the given minimum.
max: Number, creates a validator that checks if the value is less than or equal to the given maximum.
enum: Array, creates a validator that checks if the value is strictly equal to one of the values in the given array.

DATE:

min: Date
max: Date

36
Q

How do you add custom data validators on the mongoose Schema?

A

In order to add a custom validator, we need to add a validation function in the options schema for a specific field.
This function should either true or false. If it returns false, then it means there is an error, true - the input can be accepted.
There is one important caveat - this custom validator function will run only for newly created documents.

validate: function () {}

If we want to define a message that is sent when the validation does not pass, we need to put validator and the message inside of the validate object.

  validate: {
    validator: function (val) {
      return val < this.price;
    },
    message: 'Discount price ({VALUE}) should be below regular price'
  },

({VALUE}) is a property that is internal to mongoose and replaces the placeholder with actual value.

37
Q

How can you handle creation of users with special roles, like admin?

A

When creating user, we need to make sure, that the User.create method that we await in the signup function as an argument doesn’t take unwanted fields. So we do not want to pass the whole req.body to the User.create, but specify the fields.

  const newUser = await User.create({
    name: req.body.name,
    email: req.body.email,
    password: req.body.password,
    passwordConfirm: req.body.passwordConfirm
  });

In order to assign roles, we can edit an User in the mongo db.
Or create a special route for creating admin users.

38
Q

How do you create instance methods on mongoose Schemas

A

Instance method is a method that will be available on all documents of a certain collection. In instance method, this always points to the current document.

userSchema.methods.function_name = function (){}

userSchema.methods.correctPassword = async function(
candidatePassword,
userPassword
) {
return await bcrypt.compare(candidatePassword, userPassword);
};

39
Q

What is the distinction between types of relationships in mongodb and how is it different from relational databases? (1:1, 1:*)

A

There are 3 major types of relationships between data:

1: 1
1: * (many)
* :* (many to many)

In relational databases, we stop at many distinction. However, when we take into account non relational databases like mongodb, then many can be then divided into:

  • few (few, dozens)
  • many (hundreds, thousands of docs)
  • ton (milions…)
40
Q

Whtat is the difference between embedding and referencing?

A
  • Referenced / Normalized data stores the related datasets and documents separated. We store the reference to another documents as children (form field inside of a document). If, we need to make a connection between the documents, then the query gets longer.
    In relational databases, all data is normalized, meaning we cannot embed documents.
  • In embedded / denormalized data model, we embed the data (documents) into the main document. We have all relevant data right inside that document without the need to have separate documents, ids and collections.
    + Performance: our app will need to make fewer queries to get all the information
  • Impossible to query the embedded document on its own

Summary:
In general, always favor embedding, unless there is good reason not to embed.
1:TON, 1:MANY relationship is usually a good reason to reference instead of embedding.
Favor referencing, when data is updated a lot and you need to access a dataset on its own.

41
Q

How do we decide, whether we should use referencing or embedding (steps - framework)?

A
  1. Relationship type (How datasets are related to each other)
    Embed when there are 1:FEW, 1:MANY relationships
    Reference when there are 1:MANY, 1:TON, MANY:MANY
  2. Data access patterns (how often data is read and written. read/write ratio)
    Embed when there is high read/write ratio (data is mostly read, does not change quickly)
    Reference when low rear/write ratio (data is updated a lot)
  3. How much the data is really related. How we want to query?
    Embed, when dataset really belong together
    Reference, if we frequently need to query both datasets on their own.
42
Q

What are the types of referencing that we can do?

A
  1. Child referencing - we keep references to the related child documents in a parent document. Usually stored in an Array. However, this array might grow very big (if we log our errors (millions), then the array suddenly becomes very big, so this is an anti pattern in mongodb). 1:FEW
  2. Parent referencing - in each child document, we keep a reference to the parent document. The child always knows its parent, but the parent does not know their children (how many there are, who they are). This makes this data model isolated. 1:MANY, 1:TON
  3. Two-way referencing - MANY:MANY relationships
    In document #1 we keep references to #2 and #3, but at the same time in documents #2 and #3, we keep the reference to document 1.
    This makes it really easy to search for the documents completely independently.
43
Q

How does geospatial data work in mongodb?

A

MongoDB supports geospatial data out of the box. Geospatial data is basically data that describes places on earth using longitude and latitude coordinates.
We can describe points, but also more complex geometries, like lines, polygons, multipolygons.

MongoDB uses special data format called GeoJSON in order to specify geospatial data.

44
Q

How do you implement geospatial data in mongoose?

A

MongoDB uses special data format called GeoJSON in order to specify geospatial data. When we define a location on the Schema, opposed to previous cases, we do not attach schema options in an object.
In this case, we attach a GeoJSON object.

startLocation: {
      // GeoJSON
      type: {
        type: String,
        default: 'Point',
        enum: ['Point']
      },
      // longitude, latitude
      coordinates: [Number]
    }
45
Q

How can you embed documents in mongoose Schema

A

There are two ways of embedding the documents in mongoose.
1) Create a new document and embed data in it - in this case the embedded array will generate documents for each option, each having their own id.

locations: [
      {
        type: {
          type: String,
          default: 'Point',
          enum: ['Point']
        },
        coordinates: [Number],
        address: String,
        description: String
      }
    ]

2) We can embed documents that are already in the db. In this solution we need to create a pre save hook that will map the ids that were passed in the array and embed the data from the documents.
SCHEMA:
guides: Array

PRESAVEHOOK:
tourSchema.pre(‘save’, async function(next) {
const guidesPromises = this.guides.map(async id => await User.findById(id));
this.guides = await Promise.all(guidesPromises);
next();
});

Then, in the request, we simply pass an array of string ids

46
Q

How can you implement referencing using mongoose

A

When we reference documents using mongoose, the references will be stored in an Array filled with ObjectId(“id”).

guides: [{ type: mongoose.Schema.ObjectId, ref: ‘User’ }]

For this, we actually do not need the User model to be imported in the module.

Because the document, will just store the references to other documents, we should use a populate process, so we get access to the child documents, whenever we query certain parent document.

47
Q

How do we populate the references fields using mongoose?

A

Populate process happens in a query. In the function that we get some data, we need to add a .populate() function and pass as arguments, the fields that we would like to populate.
Behind the scenes populate function adds additional queries, because whenever we populate a result, we need to query for the result that is referenced in the ObjectId field

const tour = await Tour.findById(req.params.id).populate(‘guides’);

instead of just a field name, we can pass an options object:
{path: ‘guides’, select: ‘-__v -passwordChangedAt’)} if we want to for example exclude somefields from the query.

in mongoose, you can pass 2 strings joined by a ‘ ‘ to populate multiple fields. Or simply use populate twice.

A good idea might be to add a query middleware, that will run for all /^find/ queries.

48
Q

What is virtual populate in mongoose?

A

Virtual populate is a feature that allows to populate a document with that does not have reference to its children (when we use parent referencing). It solves the problem of child referencing, because we do not store the array of ids in the document without persisting it to the database.

tourSchema.virtual('reviews', {
  ref: 'Review',
  foreignField: 'tour',
  localField: '_id'
});

Then we need to populate the fields in the controller. Like

const tour = await Tour.findOneById(req.params.id).populate(‘reviews’)

49
Q

How do you add a pre findByIdAndUpdate hook, that updates fields in the document when findByIdAndUpdate method is used on the Schema.

A

There is no hook for findByIdAndUpdate. findByIdAndUpdate uses findOneAndUpdate behind the scenes, so its findOneAndUpdate hook.

In latests mongo db releases, there is a method, that can be invoked on the query object inside the middleware / hook.
setUpdate()

todoSchema.pre(‘findOneAndUpdate’, function (next) {
this.setUpdate({ $set: { lastModifiedOn: Date.now() } });
next();
});

We can always, try to stick to .save() method on the schema instance.

Another workaround that we might want to implement is to create a pre /^findOneAnd/ hook, that assigns the value of findOne to this.r (this.review)

reviewSchema.pre(/^findOneAnd/, async function(next) {
this.r = await this.findOne();

  next();
});
// Invoke the function in the query middleware (post hook)
reviewSchema.post(/^findOneAnd/, async function() {
  // await this.findOne(); does NOT work here, the query has already been executed.
  await this.r.constructor.calcAverageRatings(this.r.tour);
});
50
Q

Explain indexing in mongodb.

A

Without indexes, MongoDB must perform a collection scan, i.e. scan every document in a collection, to select those documents that match the query statement. If an appropriate index exists for a query, MongoDB can use the index to limit the number of documents it must inspect.

Indexes are basically an ordered list of the fields that are stored outside of the collection, and we can traverse them much quicker.

Indexes, are a great option to increase read performance, when an app grows. However, it may happen that they are inefficient if we set it up not correctly.
We need to study access patterns to our application (check which fields are queried the most) and set the indexes for these fields.

Each index uses resources and needs to be updated each time and underlying collection is updated (if a collection has high write/read ratio), then it basically makes no sense

51
Q

How do you set a Single field index on a Schema in mongoose?

A

In order to set up index on Schema in mongoose, we need to add invoke index on the Schema in Model.

tourSchema.index({ price: 1 });
(1 - ascending, -1 - desc)

mongoose, by default, sets up indexes for all the fields that are unique.

In order to examine, the query, number of documents searched etc., we can add .explain() method, to the query object in the handler function like:

const doc = await features.query;

52
Q

What is Compound index and how do you set it up in mongoose?

A

MongoDB supports compound indexes, where a single index structure holds references to multiple fields [1] within a collection’s documents.

tourSchema.index({ price: 1, ratingsAverage: -1 });

When it comes to compound indexes, we can also attach an options object, that makes a combination of fields in the index unique.

reviewSchema.index({ tour: 1, user: 1 }, { unique: true });

53
Q

What are static methods in mongoose and how can we make use of them?

A

Static methods, are methods that are defined on the mongoose.Schema. The context of this, is the Model, so they might come in handy in certain situations.
In order to define a static method, we need to assign a function to reviewSchema.statics.functionName = function (tourId){}

We can create a middleware (post save hook) that updates a document with a static function. It might be useful to use aggregation pipeline and findOneAndUpdate method.

reviewSchema.statics.calcAverageRatings = async function(tourId) {
  const stats = await this.aggregate([
    {
      $match: { tour: tourId }
    },
    {
      $group: {
        _id: '$tour',
        nRating: { $sum: 1 },
        avgRating: { $avg: '$rating' }
      }
    }
  ]);
  await Tour.findByIdAndUpdate(tourId, {
    ratingsQuantity: stats[0].nRating,
    ratingsAverage: stats[0].avgRating
  });
};

reviewSchema.post(‘save’, function() {
this.constructor.calcAverageRatings(this.tour);
});

54
Q

What are setter functions that can be attached to the field on Schemas?

A
Defining a setter function, makes it so, that this callback function will be automatically invoked always after a specific field is saved. The return value of that callback function will be what actually is saved.
// rounding
      set: val => Math.round(val * 10) / 10
55
Q

How do you implement geospatial queries to find locations within radius?

A

In order to do that, we need to make use of a specific query operator called $geowithin. (there are other operators such as $near)

const tours = await Tour.find({
    startLocation: { $geoWithin: { $centerSphere: [[lng, lat], radius] } }
  });
radius, needs to be converted to radiants, since it is the unit that mongodb uses to calculate the sphere, to find out what are the distances.
  const radius = unit === 'mi' ? distance / 3963.2 : distance / 6378.1;

In our query string, we should implement
params that get the {latlng, distance, unit}

What is more, we need to set up an index on the location and :

tourSchema.index({ startLocation: ‘2dsphere’ });

56
Q

How do you implement geospatial queries to find locations within radius?

A

In order to do that, we need to make use of a specific query operator called $geowithin. (there are other operators such as $near)

const tours = await Tour.find({
    startLocation: { $geoWithin: { $centerSphere: [[lng, lat], radius] } }
  });
radius, needs to be converted to radiants, since it is the unit that mongodb uses to calculate the sphere, to find out what are the distances.
  const radius = unit === 'mi' ? distance / 3963.2 : distance / 6378.1;

In our query string, we should implement
params that get the {latlng, distance, unit}

What is more, we need to set up an index on the location and :

tourSchema.index({ startLocation: ‘2dsphere’ });

57
Q

How can you use aggregation pipeline for geospatial data to calculate distances?

A

For geospatial aggregation, there is actually only one single stage: we need to define a stage called $geoNear

58
Q

How can you import your models inside your project using mongoose?

A

Apart from importing models to the controllers through require references, you can import the models to mongoose instance.
Mongoose uses a concept called singleton, so in the file, in which we start up the connection to DB, we can require (import) all of our models.

require(‘./models/Store’);

then the models will be accessible on
mongoose.model(‘Store’)

59
Q

How can you query indexed text fields in mongoose?

A

In order to efficiently search in indexed fields that have a compound index, we should set up an index on the model.

storeSchema.index({
name: “text”,
description: “text”,
});

“text” indicates that we will be performing fulltext search queries on that fields.

In our API, we can set up an endpoint that will allow for search queries.

exports.searchStores = async (req, res) => {
  const query = Store.find(
    {
      $text: {
        $search: req.query.q,
      },
    },
    {
      score: { $meta: "textScore" },
    }
  ).sort({
    score: { $meta: "textScore" },
  });
  const stores = await query;
  res.json(stores);
};

In the query we make use of the search object and sort the values that are given by “textScore” meta field