Step 11

Open NoteDao.js file inside server/data folder. We don't need the constructor of this class! We will be storing the data in MongoDB rather than an array.

Delete the constructor. Then, update the operations as follows:

Create

  async create({ title, text }) {
    if (title === undefined || title === "") {
      throw new ApiError(400, "Every note must have a none-empty title!");
    }

    if (text === undefined) {
      throw new ApiError(400, "Every note must have a text attribute!");
    }

-   const note = new Note(title, text);
-   this.notes.push(note);
+   const note = await Note.create({ title, text });
    return note;
  }

Update

  async update(id, { title, text }) {
-   const index = this.notes.findIndex((note) => note._id === id);
+   const note = await Note.findByIdAndUpdate(
+     id,
+     { title, text },
+     { new: true, runValidators: true }
+   );
    

-   if (index === -1) {
+   if (note === null) {
      throw new ApiError(404, "There is no note with the given ID!");
    }

-   if (title !== undefined) {
-     this.notes[index].title = title;
-   }

-   if (text !== undefined) {
-     this.notes[index].text = text;
-   }

-   return this.notes[index];
+   return note;
  }

The findByIdAndUpdate is a built-in function by Mongoose. It takes three parameters:

  • id: the ID of a note in your database to be updated. If id does not match an existing note, the findByIdAndUpdate will return null.

  • An object containing the new attributes (and their values) which are to replace the existing attribute values of the note to be updated. If any of these are undefined, the attribute will not change (so we don't need this if statements to guard against this scenario)

  • An object of parameters:

    • new: true changes the default behavior of findByIdAndUpdate to return the updated note (instead of the original one).
    • runValidators: true changes the default behavior of findByIdAndUpdate to force running validators on new attributes. If validation fails, the findByIdAndUpdate operation throws an error.

Delete

  async delete(id) {
-   const index = this.notes.findIndex((note) => note._id === id);
+   const note = await Note.findByIdAndDelete(id);

-   if (index === -1) {
+   if (note === null) {
      throw new ApiError(404, "There is no note with the given ID!");
    }

-   const note = this.notes[index];
-   this.notes.splice(index, 1);
    return note;
  }

The findByIdAndDelete will delete and return the deleted note if the id exists in the database. Otherwise, it will return null.

Read

  // returns an empty array if there is no note with the given ID
  async read(id) {
-   return this.notes.find((note) => note._id === id);
+   const note = await Note.findById(id);
+   return note ? note : [];
  }

The findById will return null if there is no note with the give ID.

  // returns an empty array if there is no note in the database
  //  or no note matches the search query
  async readAll(query = "") {
    if (query !== "") {
-     return this.notes.filter(
-       (note) => note.title.includes(query) || note.text.includes(query)
-     );
+     const notes = await Note.find().or([{ title: query }, { text: query }]);
+     return notes;
    } 
-   return this.notes;
+   const notes = await Note.find({});
+   return notes;
  }

The find method takes an optional parameter, a filter, which can be used to search for notes that match the given attribute values. If we want to receive all notes, we can call find with no argument or with an empty filter object.

Notice I have use the or method, part of Mongoose's query builder language.

Note that the current query returns exact matches. You can also use regular expression to perform an contains query.

- const notes = await Note.find().or([{ title: query }, { text: query }]);
+ const notes = await Note.find().or([{ title: { "$regex": query, "$options": "i" } }, { text: { "$regex": query, "$options": "i" } }]);

If there are no "notes" in the database, or there is no match for the filter we have provided, the find method returns an empty array.

Here is the NodeDao.js after all the refactoring!
const Note = require("../model/Note");
const ApiError = require("../model/ApiError");

class NoteDao {
  constructor() {
    this.notes = [];
  }

  async create({ title, text }) {
    if (title === undefined || title === "") {
      throw new ApiError(400, "Every note must have a none-empty title!");
    }

    if (text === undefined) {
      throw new ApiError(400, "Every note must have a text attribute!");
    }

    const note = await Note.create({ title, text });
    return note;
  }

  async update(id, { title, text }) {
    const note = await Note.findByIdAndUpdate(
      id,
      { title, text },
      { new: true, runValidators: true }
    );

    if (note === null) {
      throw new ApiError(404, "There is no note with the given ID!");
    }

    return note;
  }

  async delete(id) {
    const note = await Note.findByIdAndDelete(id);

    if (note === null) {
      throw new ApiError(404, "There is no note with the given ID!");
    }

    return note;
  }

  // returns an empty array if there is no note with the given ID
  async read(id) {
    const note = await Note.findById(id);
    return note ? note : [];
  }

  // returns an empty array if there is no note in the database
  //  or no note matches the search query
  async readAll(query = "") {
    if (query !== "") {
      const notes = await Note.find().or([{ title: query }, { text: query }]);
      return notes;
    }
    const notes = await Note.find({});
    return notes;
  }
}

module.exports = NoteDao;