Reference

  1. The schema part of the associated code

    In the schema we care about two pieces of information: the type of the association id (type), and which collection the association is (ref). The association of mongoose can only store id, and cannot store other fields because it is not supported. If you want to store fields other than id, you need to write the corresponding logic to get the fields.


    If it is to many, it is necessary to use association attribute s: [{ }], if it is one to one, it is necessary to Use Associative property: { } .


    Example for many-to-many bidirectional association:

    1
    2
    3
    4
    5
    //schema 1
    courses: [{
    type: mongoose.Types.ObjectId, //If the id type is String, write String.
    ref: 'Course' //The model name associated with the collection, note that it is capitalized.
    }]
    1
    2
    3
    4
    5
    //schema 2
    students: [{
    type: mongoose.Types.ObjectId,
    ref: 'Student'
    }]


  1. The controller part of the associated code

    Divided into two parts: add association and delete association.

    1. Add association

      Note here that in a many-to-many relationship, addCourseToStudent() and addStudentToCourse() are the same thing. Here you need to get the id information of the two parties you want to associate, and this information is written on the url: /v1/students/:id/courses/:code. If it is to add an association, the method is POST.

      What needs to be guaranteed is that the variable names (:id, :code) on the url must be consistent with those in const { } = req.params.

      After getting the id, first verify whether the (two) ids exist. In addition, since two id objects need to be used to verify the existence of the function, it is necessary to import the schema of another id object, such as const Course = require('../modules/course');


      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      async function addCourseToStudent(req, res){
      const { id, code } = req.params; //Take two ids
      const student = await Student.findById(id).exec(); //Verify that id1 exists
      const course = await Course.findById(code).exec(); //Verify that id2 exists

      if (!student || !course) { //Process when there is no id
      return res.status(404).json({
      error: 'Category or Subcategory not found',
      });
      }

      student.courses.addToSet(course._id); //It is recommended to use addToSet instead of push to avoid repetition
      course.students.addToSet(student._id); //Two-way binding
      //If it is a one-to-one relationship, that is, the associated id is not an array, it only needs to be written as student.course = course._id

      await student.save(); //save
      await course.save(); //save

      return res.json(student);
      }

      After writing, export this function and register the route in the routing file.

      1
      studentRouter.post('/:id/courses/:code', addCourseToStudent)

    2. Remove association

      is very similar to adding. Just change the function name in the add code to removeCourseToStudent and then addToSet() to pull(). Also, the request method should be changed from POST to DELETE.

      There are two logics for deletion, one is to delete whether there is a connection or not; the other is to detect whether there is a connection first, and then delete it. Usually don’t care, because the front end will know whether there is a relationship when fetching data, and the delete option will pop up when there is a relationship.


      If it is a one-to-one relationship, simply reassign the resource of ref to undefined.


  2. Schema and path issues for two-way binding

    In two-way binding, the ref in the schema must be written by both parties. However, in the controller and router, only one of them needs to be coded, because when the unilateral controller is coded, the schema of both sides will be directly introduced, and _id will be added to both sides and saved on both sides.


  3. Get the associated data after creating the association: populate()

    In the getXXXById() function in the controller file, add populate('field') after the findById(id) function.

    1
    2
    3
    4
    5
    6
    async function getStudentById(req, res){
    const {id} = req.params;
    const student = await Student.findById(id).populate('course').exec();
    if(!student){return ...};
    res.json(student);
    }

    If the document corresponding to the query id is not associated, populate() will not report an error, but only the associated item will not be displayed.

    The principle here is that the principle of populate is that mongoose sends two requests to the database and splices them together. This is determined by the database structure of mongoose. All data cannot be obtained in one request, so splicing exists on the server side. Instead, sql is stitched together in the database. But mongodb also supports database-side splicing processing, which uses aggregation. Later optimization may be used, and populate can be used in the initial stage.


    populate() also supports fetching only a subset of fields. Same as MongoDB section 2.3.5. (id will be retrieved by default)

    1
    const student = await Student.findById(id).populate('course','name description') .exec();

    populate() can also fetch multiple nested data with two-paragraph chaining, but this is not recommended. However, this is the only way to fetch multiple associated resources. Because a single populate() can only be selected from the same resource, even with multiple attributes.


  4. Scenarios for populate()

    When fetching all the data, generally only metadata is needed, so there is no need to populate (resource waste). Generally, populate() is done when fetching a specific document.

    If two resources are associated and you want to fetch them at the same time, you can call populate() in a chain.


  5. Decide which fields populate() selects in the front end

    Sometimes there are too many fields associated with the resource, and the required fields must be selected from the front end (API control).

    1
    2
    3
    4
    5
    6
    7
    async function getStudentById(req, res){
    const { id } = req.params;
    const { fields } = req. query;
    const student = await Student.findById(id).select('').populate('course').exec(); //Get the student's
    if(!student){return ...};
    res.json(student);
    }


  1. Remove related associations when deleting resources

    When mongoose finds that the associated resource does not exist, it will not report an error. But this results in a lot of invalid data being present.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    async function deleteStudentById(req, res){
    const {id} = req.params;
    const student = await Student.findByIdAndDelete(id).exec();
    if(!student){return...};

    await Course.updateMany({ students:student._id}, { //:?.?
    $pull:{
    students:student._id
    }
    }).exec();
    res.sendStatus(204);
    }

    Both sides of a bidirectional association delete the association. The code logic is the same.


    In the one-to-one relationship, because it is not an array, $pull cannot be used, otherwise the error “MongoServerError: Cannot apply $pull to a non-array value” will appear. The $unset operator should be used instead, with the same syntax.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    async function deleteRootCategoryId(req, res) {
    const { id } = req.params;
    const rootCategory = await RootCategory.findByIdAndDelete(id).exec();
    if (!rootCategory) {}

    await SubCategory.updateMany({ parentCategory: rootCategory._id }, {
    $unset: {
    parentCategory: rootCategory._id
    }
    }).exec()
    res.sendStatus(204);
    }

  1. Summary

    The associated code is divided into three parts: schema (two-way and two-way), controller (two-way one-way/two-way), and router (two-way one-way).

    In the controller, there are three parts to be added and rewritten:

    1. Add an association function (two-way and one-way)

    2. Delete the associated function (two-way and one-way)

    3. The function to find a single resource plus populate() (both sides)

    4. Delete the function of a single resource plus delete the associated code (both sides)


Share