React Native: MongoDB Aggregation with Stack Navigation
By: Ross Bulat
Tags: JavaScript, Software Engineering, Programming, React, React Native

MongoDB Aggregation

MongoDB Aggregation not only minimises syntax, the operations of which are optimised for their particular tasks, built directly into Mongo. This offers us better performance and less syntax than relying on alternative means outside of the Mongo API.

Where your CRUD operations can seem limiting to work with, the Aggregation Pipeline offers an expansion of tools for processing and grouping documents over a range of processes, and returning a result of these pipeline operations.

Single Purpose Aggregation Operations

The simplest of Aggregation features within MongoDB are what are termed Single Purpose Aggregation Operations. These are essentially complex aggregation jobs wrapped into a simple function call. There are 3 single purpose aggregation operators to note here that can be useful for a range of use cases, but more importantly, give us a feel for the additional power aggregation offers us:

// estimates document count of a collection using metadata

db.collection.estimatedDocumentCount() // gets an accurate document count in a collection

db.collection.countDocuments(query, options); // finds distinct values of a given field in a collection of document

db.collection.distinct(field, query, options);

So instead of using CRUD operators or incrementation to ascertain a document count, we can use the functions outlined above, either quickly returning an estimated count, or getting a reliable and accurate count.

The distinct() function also saves us looping and filtering, or writing complex find() statements to get distinct values of a particular field in a collection — the function will return an array of unique values for a particular field on the fly.

Where aggregation gains flexibility is in the pipeline, and the aggregate() method itself, giving the developer much more granularity of how the aggregation processes.

Aggregation Pipeline: Stages and Operators

If you can, imagine a conveyer belt that carries our data down a factory assembly line; this conveyer belt is our aggregation pipeline:

Aggregation pipeline as an assembly facility

Each worker standing at their stations at various locations down the conveyer belt will manipulate our data in some way; these workers in MongoDB terminology are called stages. At each stage, our data is manipulated — and there are a few ways we can go about doing that.

The entire library of Pipeline Aggregation Stages at our disposal are documented here in the official Mongo documentation.

Not only this, within each stage we can configure how the stage will work with the input data in question. We do this with what we call Operators. The full list of Aggregation Pipeline operators can be found here.

Stages are high level data manipulations, whereas operations are more granular calculations that can assist certain stages.

Examples of stage and operator usage

An aggregation pipeline in Javascript gives us a similar construction to the conveyer belt analogy, where each stage is listed in an array within the aggregate() function. Within each stage, we can include operations to assist in further data manipulation:

const docs = db.collection(col).aggregate([

{ $stage: { operations }},

{ $stage: { operations }},

...

])

Not all operators are used with all stages. This will become apparent in the following examples.

So theory out of the way, let’s check out some tangible aggregation examples.

A common stage used in an aggregation is the $match stage, that can filter what is returned from a document:

// fetching a range of products by category db.products.aggregate([

{ $match : { category : "programming" } }

]) > {

"_id" : ObjectId(...),

"seller" : "Ross",

"category" : "programming",

"sales" : 100

}

...

Another common stage is $group , that outputs a different structure of your documents. $group is often combined with operators to return additional calculations on the input documents, such as the $sum operator:

// returning the total products using `$sum` operator db.products.aggregate([

{

$group: {

_id: null,

count: { $sum: 1 }

}

}

]) > { "_id" : null, "count" : 42 }

The _id field of a document determines a unique output document. In the case where id is always null , there will only ever be one output document, an hence calculate the total products in its count field.

Let’s take this one step further before continuing. We will now configure _id to match a particular field value of a document, and introduce multiple stages of an aggregation by using the $group and $match stages together. Can you recognise what is being calculated?:

db.sales.aggregate([

{

$group: {

_id : "$item",

totalRevenue: {

$sum: {

$multiply: ["$price", "$quantity"] }

}

}

}, {

$match: { "totalRevenue": { $gte: 1000.00 } }

}

])

This aggregation aims to only return high revenue generating products, being products that have generated more than $1000 through sales.

Looking at the $group stage now, our output documents are unique based on the item field of our input documents. For every unique item sold, we are using the $sum and $multiply operators to calculate the total revenue made by that product. $group outputs documents of _id and totalRevenue fields only:

// $group returns item with total revenue [

{ _id: "Ross's Programming Book 2019", totalRevenue: 890 },

{ _id: "Ross's Programming Book 2020", totalRevenue: 45800 },

...

]

In the second stage, $match filters this result, only returning documents with a totalRevenue of 1000 or more. In the above case, Ross’s 2019 book will be filtered out.

If we refer to the conveyer belt analogy, we can slot in our stages and operators next to each assembler worker now — I have also added the $sample stage, that we’ll cover next:

Be sure to read the official documentation for the full operator reference, that also hosts a wealth of examples.

But What About Random Sampling Aggregation?

For our random sampling, the key stage in our aggregation is the $sample stage. As the name suggests, it returns a random sample of documents based on its input.

The input for the $sample stage is actually very simple, only requiring a size field to determine how many documents will be returned:

{ $sample: { size: <positive integer> } }

So if we wanted to fetch a random sample of products, we could simply run the following:

// return a random sample of products db.products.aggregate([

{ $sample: { size: 10 } }

])

Of course, you are not limited to only just this $sample stage. If you are curious to extend an aggregation to your particular use case at this stage of the article, see if you can do so using the stages and operators we’ve mentioned here — and others in the documentation that suit your needs.

With aggregation now understood you can summon a random list of entries for your app to display. Let’s assume we wish to display products from here, and explore how to embed these results in a React Native app via a Navigator.