Home > Software design >  MongoDB, Mongoose: How to refer to a document inside an array of another document?
MongoDB, Mongoose: How to refer to a document inside an array of another document?

Time:01-25

I have been working with MongoDB for just over a month, and I do not have much experience with Relational Databases as well. I would try to explain what I am trying to do below.

I have a schema in mongoose like this:

import mongoose, { model, Schema } from "mongoose";
import { IBudget } from "../types/budget";
import User from "./user.model";

const budgetSchema = new Schema<IBudget>({
  user: { type: mongoose.Schema.Types.ObjectId, required: true, ref: User },
  categories: {
    type: [
      {
        title: { type: "string", required: true, trim: true },
        amount: { type: "number", required: true, default: 0 },
        color: { type: "string", required: true },
        managed: { type: "boolean", required: true, default: true },
        editable: { type: "boolean", required: true, default: true },
        description: { type: "string", maxlength: 120 },
      },
    ],
    required: true,
    maxlength: [12, "Cannot have more than 12 categories"],
    minlength: [1, "Need at least 1 cateogry"],
  },
  month: {
    type: Number,
    required: [true, "Please add month"],
    min: 1,
    max: 12,
  },
  year: { type: Number, required: [true, "Please add the year"] },
});

const Budget = model("Budget", budgetSchema);

export default Budget;

With this, when I create budget document using Budget.create({...budgetObject}), I get the following document created in the database. enter image description here

I have another schema called Expense like this:

import mongoose, { model, Schema } from "mongoose";
import { IExpense } from "../types/expense";
import User from "./user.model";

const expenseSchema = new Schema<IExpense>(
  {
    title: { type: String, required: true, trim: true, maxlength: 40 },
    description: { type: String, trim: true, maxlength: 260 },
    expenseDate: { type: Date, default: Date.now },
    category: {
      type: String,
      required: [true, "Please add the category name"],
    },
    user: { type: mongoose.Schema.Types.ObjectId, required: true, ref: User },
    amount: { type: Number, required: true },
    reverted: { type: Boolean, required: true, default: false },
  },
  { timestamps: false }
);

const Expense = model("Expense", expenseSchema);
export default Expense;

My Question is: How do I refer the category of the expense document to one of the items in the categories array within the Budget document?

I know how to refer to other documents by using { type: mongoose.Schema.Types.ObjectId, required: true, ref: User }, as you have seen. But I don't understand how that would work for a subdocument or a document inside of an array field nested in another document.

I am not even sure whether this is possible, in which case, an alternative approach for the same behavior would be highly appreciated.

CodePudding user response:

It looks like you've learnt a lot in a month :-).

For your use case, instead of hardcoding the category field in the budgetSchema, you should first create a categorySchema like:

import { model, Schema } from "mongoose";
import { ICategory } from "../types/category";

const categorySchema =
  new Schema() <
  ICategory >
  {
    title: { type: "string", required: true, trim: true },
    amount: { type: "number", required: true, default: 0 },
    color: { type: "string", required: true },
    managed: { type: "boolean", required: true, default: true },
    editable: { type: "boolean", required: true, default: true },
    description: { type: "string", maxlength: 120 },
  };

const Category = model("Category", categorySchema);

export default Category;

Then in your budgetSchema, your categories field will be an array of categorySchema object ids as such:

import mongoose, { model, Schema } from "mongoose";
import { IBudget } from "../types/budget";
import User from "./user.model";
import Category from "./category.model";

const budgetSchema =
  new Schema() <
  IBudget >
  {
    user: { type: mongoose.Schema.Types.ObjectId, required: true, ref: User },
    categories: [
      { type: mongoose.Schema.Types.ObjectId, required: true, ref: Category },
    ],
    month: {
      type: Number,
      required: [true, "Please add month"],
      min: 1,
      max: 12,
    },
    year: { type: Number, required: [true, "Please add the year"] },
  };

const Budget = model("Budget", budgetSchema);

export default Budget;

Since the categories are now documents of a separate schema, you'll now be able to reference them in your expenseSchema like this:

import mongoose, { model, Schema } from "mongoose";
import { IExpense } from "../types/expense";
import User from "./user.model";
import Category from "./category.model";

const expenseSchema =
  new Schema() <
  IExpense >
  ({
    title: { type: String, required: true, trim: true, maxlength: 40 },
    description: { type: String, trim: true, maxlength: 260 },
    expenseDate: { type: Date, default: Date.now },
    category: {
      type: mongoose.Schema.Types.ObjectId,
      required: [true, "Please add the category name"],
      ref: Category,
    },
    user: { type: mongoose.Schema.Types.ObjectId, required: true, ref: User },
    amount: { type: Number, required: true },
    reverted: { type: Boolean, required: true, default: false },
  },
  { timestamps: false });

const Expense = model("Expense", expenseSchema);
export default Expense;
  • Related