Aggregations
Aggregations are powerful operations that process data records and return computed results. They allow you to analyze data, perform calculations, and transform documents through a series of stages called a pipeline.
With RESTHeart, you can easily execute MongoDB aggregations through simple REST API calls.
Running Aggregations
To execute an aggregation, send a GET
request to:
GET /collection/_aggrs/aggregation-name?avars={"var1":"value1"}
RESTHeart will process the request and return the computed results in the response body.
Tip
|
By default, aggregation results are returned directly in the response without being written to the database. For persistent results, use Materialized Views. |
Defining Aggregations
Aggregations must be defined in the collection’s metadata before they can be used.
Creating an Aggregation
To define an aggregation, use a PATCH
request to update the collection metadata:
PATCH /mycollection HTTP/1.1
Content-Type: application/json
{
"aggrs": [
{
"uri": "sales-by-region",
"stages": [
{ "$match": { "status": "completed" } },
{ "$group": {
"_id": "$region",
"total": { "$sum": "$amount" }
}
},
{ "$sort": { "total": -1 } }
]
}
]
}
This defines an aggregation named sales-by-region
that:
1. Filters for completed sales
2. Groups documents by region
3. Calculates the total amount for each region
4. Sorts the results by total in descending order
Aggregation Properties
Property | Description | Required |
---|---|---|
|
The name used in the URL path: |
Yes |
|
Array of MongoDB aggregation pipeline stages |
Yes |
|
Allows operations to use more than 100MB of memory |
No (default: false) |
Parameterizing Aggregations
Make your aggregations dynamic by using variables that can be passed at runtime.
The $var Operator
Use the $var
operator in your aggregation stages to reference variables:
{
"aggrs": [
{
"uri": "sales-by-product",
"stages": [
{ "$match": { "product": { "$var": "productName" } } },
{ "$group": { "_id": "$month", "sales": { "$sum": "$amount" } } }
]
}
]
}
Passing Variables
When executing the aggregation, provide variable values using the avars
query parameter:
GET /mycollection/_aggrs/sales-by-product?avars={"productName":"Widget Pro"} HTTP/1.1
This returns sales data only for the "Widget Pro" product.
Default Values
Since RESTHeart 7.3, you can specify default values for variables:
{ "$var": [ "sortField", { "date": -1 } ] }
This uses the provided sortField
value if available, otherwise defaults to sorting by date in descending order.
Example with default value:
{
"aggrs": [
{
"uri": "recent-orders",
"stages": [
{ "$sort": { "$var": [ "sortBy", { "date": -1 } ] } },
{ "$limit": 10 }
]
}
]
}
Dot Notation for Nested Variables
Since RESTHeart 7.6, you can access nested properties in variables using dot notation:
GET /mycollection/_aggrs/my-pipeline?avars={"config":{"limit":10,"skip":20}} HTTP/1.1
In the aggregation definition:
{ "$limit": { "$var": "config.limit" } }
This resolves to { "$limit": 10 }
.
Predefined Variables
RESTHeart provides several predefined variables that you can use in your aggregations:
Variable | Description |
---|---|
|
The authenticated user object (e.g., |
|
The user’s MongoDB permissions (e.g., |
|
Current page number from query parameter |
|
Page size from query parameter |
|
Calculated as |
|
Same as |
Pagination in Aggregations
Unlike regular document queries, pagination in aggregations must be handled explicitly using the $skip
and $limit
stages.
Use the predefined variables to implement pagination:
{
"aggrs": [
{
"uri": "paginated-results",
"stages": [
{ "$match": { "active": true } },
{ "$sort": { "lastName": 1 } },
{ "$skip": { "$var": "@skip" } },
{ "$limit": { "$var": "@limit" } }
]
}
]
}
Request with pagination:
GET /mycollection/_aggrs/paginated-results?page=3&pagesize=25 HTTP/1.1
This skips the first 50 documents and returns the next 25.
Conditional Stages with $ifvar
Since RESTHeart 7.3, you can include stages conditionally based on whether specific variables are provided.
Basic Conditional Stage
Include a stage only if a variable is provided:
{
"uri": "conditional-pipeline",
"stages": [
{ "$match": { "type": "product" } },
{ "$ifvar": [ "category", { "$match": { "category": { "$var": "category" } } } ] }
]
}
The second stage only applies if the category
variable is provided.
Multiple Required Variables
Require multiple variables for a stage:
{ "$ifvar": [ ["minPrice", "maxPrice"],
{ "$match": {
"price": {
"$gte": { "$var": "minPrice" },
"$lte": { "$var": "maxPrice" }
}
}
}
]}
Else Clause
Provide an alternative stage when variables are missing:
{ "$ifvar": [ "sortBy",
{ "$sort": { "$var": "sortBy" } },
{ "$sort": { "createdAt": -1 } }
]}
This sorts by the provided field if sortBy
is given, otherwise sorts by creation date.
Materialized Views
Create persistent collections based on aggregation results using the $merge
stage:
{
"aggrs": [
{
"uri": "sales-summary",
"stages": [
{ "$group": { "_id": "$category", "totalSales": { "$sum": "$amount" } } },
{ "$merge": { "into": "categorySalesSummary" } }
]
}
]
}
When this aggregation is executed, results are written to the categorySalesSummary
collection:
GET /mycollection/_aggrs/sales-summary HTTP/1.1
The response will be empty, but a new collection is created or updated:
GET /categorySalesSummary HTTP/1.1
HTTP/1.1 200 OK
[
{ "_id": "electronics", "totalSales": 253489.99 },
{ "_id": "furniture", "totalSales": 187245.50 }
]
Incremental Updates
The $merge
stage is more efficient than the older $out
stage because it can update existing documents rather than replacing the entire collection each time.
Security Considerations
RESTHeart checks variables for MongoDB operators to prevent injection attacks. This protection can be disabled in the configuration file, but this is strongly discouraged.
mongo:
aggregation-check-operators: true # Default setting
Transaction Support
Execute aggregations within a transaction by including the sid
and txn
parameters:
GET /mycollection/_aggrs/my-pipeline?sid=session-id&txn=transaction-id HTTP/1.1
See the Transactions documentation for details.
Example Use Cases
Monthly Sales Report
{
"uri": "monthly-sales",
"stages": [
{ "$match": {
"date": {
"$gte": { "$var": [ "startDate", { "$date": "2023-01-01T00:00:00Z" } ] },
"$lte": { "$var": [ "endDate", { "$date": "2023-12-31T23:59:59Z" } ] }
}
}
},
{ "$group": {
"_id": { "$dateToString": { "format": "%Y-%m", "date": "$date" } },
"count": { "$sum": 1 },
"totalAmount": { "$sum": "$amount" }
}
},
{ "$sort": { "_id": 1 } }
]
}
User Activity Analytics
{
"uri": "user-activity",
"stages": [
{ "$match": { "userId": { "$var": "userId" } } },
{ "$group": {
"_id": "$activityType",
"count": { "$sum": 1 },
"lastActivity": { "$max": "$timestamp" }
}
},
{ "$sort": { "count": -1 } }
]
}
Map-Reduce (Deprecated)
Warning
|
Map-reduce operations are deprecated in MongoDB. Use aggregation pipelines instead. |
For legacy systems, RESTHeart still supports map-reduce:
{
"type": "mapReduce",
"uri": "word-count",
"map": "function() { var words = this.text.split(' '); words.forEach(function(word) { emit(word.toLowerCase(), 1); }); }",
"reduce": "function(key, values) { return Array.sum(values); }",
"query": { "type": { "$var": "documentType" } }
}
Variables can be used in both the query and JavaScript functions. In JavaScript functions, use JSON.parse($vars)
to access variables:
function() {
var minLength = JSON.parse($vars).minLength || 0;
var words = this.text.split(' ');
words.forEach(function(word) {
if (word.length > minLength) {
emit(word.toLowerCase(), 1);
}
});
}