How to work with Embedded documents and References in MongoDB

Overview

MongoDB is a NoSQL database and does not support classic relationships as in SQL databases. However, you can build references across entities in MongoDB in a couple of ways -

  • Inner objects (equivalent to embedded documents, i.e., documents nested within another document)

  • References across collections using an ObjectID field in a collection to reference an ID in another collection

For a more comprehensive overview on this topic, refer to MongoDB's docs on Embedded Data Versus References. In this guide, we will see how you can work with inner objects and references in MongoDB with Neurelo.

Inner Objects (Embedded Documents)

An embedded document in MongoDB is a document nested inside another MongoDB document. That is, it is not just a reference to another document but it is fully contained within the parent document.

In the Neurelo Schema Language and in the APIs, embedded documents can be used as innerObjects.

For example, consider the following document

{
  "_id": "5fc89ecd1e602ff6c856e811",
  "name": "Twinkle Truffles",
  "flavor": "Milk Chocolate",
  "brand": "Cadbunny's",
  "price": 2.99,
  "inStock": true,
  "nutritionalInfo": {
    "calories": 250,
    "fat": "10g",
    "sugar": "25g",
    "protein": "5g"
  }
}

Here, nutritionalInfo is an inner object.

Inner objects enhance read performance by reducing database queries and ensuring atomic updates within a single document. They are optimal for closely related data that is frequently accessed together, remains relatively static, and adheres to the 16MB MongoDB document size limit. However, they might lead to data redundancy and scalability challenges as your database grows.

The Neurelo Schema for this will be something like this -

{
  "objects": {
    "foodItem": {
      "properties": {
        "id": {
          "type": "string",
          "identifier": true,
          "sourceName": "_id",
          "sourceType": "ObjectId",
          "default": {
            "function": "auto"
          }
        },
        "name": {
          "type": "string"
        },
        "flavor": {
          "type": "string"
        },
        "brand": {
          "type": "string"
        },
        "price": {
          "type": "number",
          "format": "float"
        },
        "inStock": {
          "type": "boolean"
        },
        "nutritionalInfo": {
          "$ref": "#/innerObjects/nutritionalInfo"
        }
      }
    }
  },
  "innerObjects": {
    "nutritionalInfo": {
      "properties": {
        "calories": {
          "type": "integer"
        },
        "fat": {
          "type": "string"
        },
        "sugar": {
          "type": "string"
        },
        "protein": {
          "type": "string"
        }
      }
    }
  }
}

For guidance on building APIs using inner objects, refer to the API reference materials for REST and GraphQL.

References

References in MongoDB are used to link documents across different collections. This is a commonly used technique to represent one-to-one, one-to-many, or many-to-many relationships.

To do this, instead of embedding all data within a single document, we can reference data from other collections. For example, consider the following three documents from three different collections:

Here, there is a one to one reference between NutritionalInfo and Products. Furthermore, there is a one to many reference between Products and Reviews.

The Neurelo Schema for this will be something like this -

{
  "objects": {
     "nutritionalInfo": {
      "properties": {
        "id": {
          "type": "string",
          "identifier": true,
          "sourceName": "_id",
          "sourceType": "ObjectId",
          "default": {
            "function": "auto"
          }
        },
        "calories": {
          "type": "string"
        },
        "fat": {
          "type": "string"
        },
        "sugar": {
          "type": "string"
        },
        "protein": {
          "type": "string"
        }
      }
    },
    "products": {
      "properties": {
        "id": {
          "type": "string",
          "identifier": true,
          "sourceName": "_id",
          "sourceType": "ObjectId",
          "default": {
            "function": "auto"
          }
        },
        "name": {
          "type": "string"
        },
        "flavor": {
          "type": "string"
        },
        "brand": {
          "type": "string"
        },
        "price": {
          "type": "number",
          "format": "float"
        },
        "inStock": {
          "type": "boolean"
        },
        "nutritionalInfoId": {
          "type": "string",
          "sourceType": "ObjectId"
        },
        "reviewIds": {
          "type": "array",
          "items": {
            "type": "string",
            "sourceType": "ObjectId"
          }
        }
      }
    },
    "reviews": {
      "properties": {
        "id": {
          "type": "string",
          "identifier": true,
          "sourceName": "_id",
          "sourceType": "ObjectId",
          "default": {
            "function": "auto"
          }
        },
        "productId": {
          "type": "string",
          "sourceType": "ObjectId"
        },
        "rating": {
          "type": "integer"
        },
        "comment": {
          "type": "string"
        }
      }
    }
  }
}

Unlike embedded documents, references are more suitable for large or frequently changing data. They excel in many-to-many relationships by offering flexibility to link documents across different collections, preventing data duplication, and maintaining database normalization. However, this approach may require additional queries to access related data, potentially affecting performance.

Now, consider the same database structure as shown in the example above. To create references using Neurelo, it is important to structure your create queries in a specific order. This will ensure that all documents are created in the correct sequence to initiate the references between them. For instance,

  • You can begin by creating the NutritionalInfo document, as this does not depend on any other documents.

  • Next, you can create the Products document. Initially, you can include the nutritionalInfoId with the ID of the previously created NutritionalInfo document and initialize the reviewIds with an empty array.

  • After this, you can proceed with creating individual Reviews documents, where each document references the productId of the Products document.

  • And finally, you can update the Products document to include the _ids of the newly created Reviews documents.

For guidance on building create and update APIs, refer to the API reference materials for REST and GraphQL.

Last updated