MongoDB Aggregation

crop.png

We are all very familiar with our favourite NoSQL MongoDB. MongoDB is an open-source, NoSQL database built to simplify the storage of large, document-based, unstructured data. we will learn the basics of MongoDB aggregation to unwind, sort, group, and transform our MongoDB results. MongoDB helps us to do all these operations through aggregation pipelines which are a series of operations that process data documents sequentially, I don't want to give you MongoDB documentation, but instead, I decided to use the work I did to explain the little part I think will benefit you a lot. Okay, let's get down to brass tacks.

I have a database that has an Admin, Meals and Users document, an Admin has a one-to-many relationship with the meals document, i.e one admin can have multiple meals, and users can order meals from different admins so the order field will be populated with the meals from different admins. Below is the User schema.

import mongoose from "mongoose";

const UserSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: [true, "Please provide a name"],
      maxlength: 100,
    },
    street: {
      type: String,
      required: [true, "Please provide street"],
      maxlength: 500,
    },
    postal: {
      type: String,
    },
    city: {
      type: String,
    },
    orders: { type: Array, default: [] }
  },
  { timestamps: true }
);

export default mongoose.model("User", UserSchema);

I will be using MongoDB compass so that we can see the pipeline as we move on, The image below is from MongoDB compass, the orders field has an array of objects of meals ordered by the user. meanwhile, the meals are created by different admins. now we will use MongoDB aggregation to rearrange the document so that we can separate the meals created by a particular admin. This is important because when the admin decides to see the total orders he has, he doesn't have to see the meals from other admins.

mongo1.png

mong2.png

Unwind

We will be starting with unwind function which deconstructs an array field from the input documents to output a document for each element. Each output document is the input document with the value of the array field replaced by the element.

{
  $unwind:
    {
      path: <field path>,
      includeArrayIndex: <string>,
      preserveNullAndEmptyArrays: <boolean>
    }
}

// Path: Field path to an array field, to specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes, in our case our field path is $orders.
//includeArrayIndex: Optional. The name of a new field to hold the array index of the element. The name cannot start with a dollar sign $, in our case our field path is arrayIndex.
//preserveNullAndEmptyArrays: If true, if the path is null, missing, or an empty array, $unwind output the document. If false, if the path is null, missing, or an empty array, $unwind does not output a document. in our case, it's true because it will always contain a value. though the default value is false.

mongo3.png and then we move to match,

Match

I am using an admin here as an example and in our controller function, we will be using a parameter to expect any admin. Match filters the documents to pass only the documents that match the specified condition(s) to the next pipeline stage.

{ $match: { <query> } }

// $match: takes the field you want to match, in our case it's orders.admin
//query: takes the query condition that you use to match, in our case, it's the id of the admin that is login.

mongo4.png

next, we move to the grouping,

Group

we need to group the user and their orders together. The $group stage separates documents into groups according to a "group key". The output is one document for each unique group key.

{
  $group:
    {
      _id: <expression>, // Group key
      <field1>: { <accumulator1> : <expression1> },
      ...
    }
 }

In our case, we have two keys, the _id and the data key. the _id key holds the user's object and the data holds the meal object.

mongo5.png

Then we can export our code by clicking the export to language button.

mongo6.png

and we can select different backend languages and then copy them to our code in my case I am using node.

mongo7.png

Below is the implementation of the MongoDB aggregation function generated with the pipeline, we are now able to query orders peculiar to a particular admin. The id in the match will be gotten from the particular admin that is registered, in my case, it will be req.user

export const getAllUserAndOrders = async (req: Request, res: Response) => {
  try {
    const { id } = req.user;
    if (!id) {
      res.status(401).json({ message: "Not authorized" });
    }

    const orders = await userDb.aggregate([
      {
        $unwind: {
          path: "$orders",
          includeArrayIndex: "arrayIndex",
          preserveNullAndEmptyArrays: true,
        },
      },
      {
        $match: {
          "orders.admin": id,
        },
      },
      {
        $group: {
          _id: {
            id: "$_id",
            name: "$name",
            street: "$street",
            postal: "$postal",
            city: "$city",
            createdAt: "$createdAt",
            updatedAt: "$updatedAt",
          },
          data: {
            $push: "$orders",
          },
        },
      },
    ]);
    res.status(200).json(orders);
  } catch (error) {
    res.status(500).json({ message: "something went wrong" });
  }
};

Thank you for reading I am sure you enjoyed the reading...