Aggregations
RESTHeart Cloud🔧 Configuration
⚡ Setup Guide
To run the examples on this page, you need a RESTHeart instance.
Option 1: Use RESTHeart Cloud (Recommended)
The fastest way to get started is with RESTHeart Cloud. Create a free service in minutes:
-
Sign up at cloud.restheart.com
-
Create a free API service
-
Set up your root user following the Root User Setup guide
-
Use the configuration panel above to set your service URL and credentials
|
Tip
|
All code examples on this page will automatically use your configured RESTHeart Cloud credentials. |
Option 2: Run RESTHeart Locally
If you prefer local development, follow the Setup Guide to install RESTHeart on your machine.
|
Note
|
Local instances run at http://localhost:8080 with default credentials admin:secret
|
Aggregations are powerful operations that process data records and return computed results. You can 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:
cURL
curl -i -X PATCH "[RESTHEART-URL]/mycollection?wm=upsert" \
-u "[BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"aggrs": [
{
"uri": "sales-by-region",
"stages": [
{ "$match": { "status": "completed" } },
{ "$group": {
"_id": "$region",
"total": { "$sum": "$amount" }
}
},
{ "$sort": { "total": -1 } }
]
}
]
}'
HTTPie
echo '{
"aggrs": [
{
"uri": "sales-by-region",
"stages": [
{ "$match": { "status": "completed" } },
{ "$group": {
"_id": "$region",
"total": { "$sum": "$amount" }
}
},
{ "$sort": { "total": -1 } }
]
}
]
}' | http PATCH "[RESTHEART-URL]/mycollection?wm=upsert" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json
JavaScript
fetch('[RESTHEART-URL]/mycollection?wm=upsert', {
method: 'PATCH',
headers: {
'Authorization': 'Basic [BASIC-AUTH]',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"aggrs": [
{
"uri": "sales-by-region",
"stages": [
{ "$match": { "status": "completed" } },
{ "$group": {
"_id": "$region",
"total": { "$sum": "$amount" }
}
},
{ "$sort": { "total": -1 } }
]
}
]
})
})
.then(response => {
if (response.ok) {
console.log('Write request executed successfully');
} else {
console.error('Write request failed:', response.status);
}
})
.catch(error => console.error('Error:', error));
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
Since 9.9.0, the simplest way to provide variable values is a plain query parameter named after the $var it binds:
cURL
curl -i -X GET "[RESTHEART-URL]/mycollection/_aggrs/sales-by-product?productName=Widget%20Pro" \
-u "[BASIC-AUTH]"
HTTPie
http GET "[RESTHEART-URL]/mycollection/_aggrs/sales-by-product" \
productName=="Widget Pro" \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
const params = new URLSearchParams({ productName: "Widget Pro" });
fetch(`[RESTHEART-URL]/mycollection/_aggrs/sales-by-product?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Basic [BASIC-AUTH]'
}
})
.then(response => response.json())
.then(data => {
console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));
This returns sales data only for the "Widget Pro" product. Any query parameter that isn’t otherwise reserved by RESTHeart (filter, sort, page, pagesize, keys, hint, avars, and similar — see the legacy form below) is bound as a $var this way — you’re not limited to one, and each is parsed independently, so a single request can bind as many variables as the pipeline needs.
Each value is interpreted as JSON when it parses as one, and otherwise taken literally as a string:
| Query parameter | Bound value |
|---|---|
|
the string |
|
the number |
|
the array |
|
the object |
|
the BSON date it represents — MongoDB Extended JSON types ( |
|
Note
|
This shorthand works the same way for change stream variables, since both are bound through the same mechanism. |
Legacy: the avars Query Parameter
Before 9.9.0, all variables had to be bundled into a single JSON object passed via the avars query parameter. This form still works and remains fully supported — it’s still required if your pipeline needs a variable whose name collides with a reserved query parameter (page, sort, …), and it’s the only option against a RESTHeart instance older than 9.9.0:
cURL
curl -i -X GET "[RESTHEART-URL]/mycollection/_aggrs/sales-by-product" \
-u "[BASIC-AUTH]" \
-G --data-urlencode 'avars={"productName":"Widget Pro"}'
HTTPie
http GET "[RESTHEART-URL]/mycollection/_aggrs/sales-by-product" \
avars=='{"productName":"Widget Pro"}' \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
const params = new URLSearchParams({
avars: JSON.stringify({"productName":"Widget Pro"})
});
fetch(`[RESTHEART-URL]/mycollection/_aggrs/sales-by-product?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Basic [BASIC-AUTH]'
}
})
.then(response => response.json())
.then(data => {
console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));
You can combine both forms in the same request. If a variable name is given both as a flat query parameter and inside avars, the avars value wins.
Default Values
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
You can access nested properties in variables using dot notation. Since a flat query parameter’s value is parsed as JSON when it looks like one (see above), it can carry a whole object:
cURL
curl -i -X GET "[RESTHEART-URL]/mycollection/_aggrs/my-pipeline" \
-u "[BASIC-AUTH]" \
-G --data-urlencode 'config={"limit":10,"skip":20}'
HTTPie
http GET "[RESTHEART-URL]/mycollection/_aggrs/my-pipeline" \
config=='{"limit":10,"skip":20}' \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
const params = new URLSearchParams({
config: JSON.stringify({"limit":10,"skip":20})
});
fetch(`[RESTHEART-URL]/mycollection/_aggrs/my-pipeline?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Basic [BASIC-AUTH]'
}
})
.then(response => response.json())
.then(data => {
console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));
The legacy form, avars={"config":{"limit":10,"skip":20}}, works identically.
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:
cURL
curl -i -X GET "[RESTHEART-URL]/mycollection/_aggrs/paginated-results?page=3&pagesize=25" \
-u "[BASIC-AUTH]"
HTTPie
http GET "[RESTHEART-URL]/mycollection/_aggrs/paginated-results" \
page==3 pagesize==25 \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
const params = new URLSearchParams({
page: 3,
pagesize: 25
});
fetch(`[RESTHEART-URL]/mycollection/_aggrs/paginated-results?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Basic [BASIC-AUTH]'
}
})
.then(response => response.json())
.then(data => {
console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));
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:
cURL
curl -i -X GET "[RESTHEART-URL]/mycollection/_aggrs/sales-summary" \
-u "[BASIC-AUTH]"
HTTPie
http GET "[RESTHEART-URL]/mycollection/_aggrs/sales-summary" \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
fetch('[RESTHEART-URL]/mycollection/_aggrs/sales-summary', {
method: 'GET',
headers: {
'Authorization': 'Basic [BASIC-AUTH]'
}
})
.then(response => response.json())
.then(data => {
console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));
The response will be empty, but a new collection is created or updated:
cURL
curl -i -X GET "[RESTHEART-URL]/categorySalesSummary" \
-u "[BASIC-AUTH]"
HTTPie
http GET "[RESTHEART-URL]/categorySalesSummary" \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
fetch('[RESTHEART-URL]/categorySalesSummary', {
method: 'GET',
headers: {
'Authorization': 'Basic [BASIC-AUTH]'
}
})
.then(response => response.json())
.then(data => {
console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));
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
Operator Injection Protection
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
Aggregation Pipeline Security
RESTHeart v9 introduces comprehensive security controls for aggregation pipelines to prevent dangerous operations:
Stage Blacklisting
Certain pipeline stages can be restricted to prevent unauthorized data access or modification:
-
$out- Writing to collections -
$merge- Merging data into collections -
$lookup- Cross-collection joins -
$graphLookup- Graph traversal operations -
$unionWith- Combining collections
Operator Blacklisting
JavaScript-executing operators are blocked by default for security:
-
$where- JavaScript filter expressions -
$function- Custom JavaScript functions -
$accumulator- JavaScript-based aggregation
Configuration Example
mongo:
aggregationSecurity:
blacklistedStages:
- $out
- $merge
- $lookup
- $graphLookup
- $unionWith
blacklistedOperators:
- $where
- $function
- $accumulator
allowCrossDatabaseOperations: false
allowJavaScriptExecution: false
When security violations occur, RESTHeart returns HTTP 403 Forbidden with specific violation types:
-
BLACKLISTED_STAGE- Attempted to use a prohibited pipeline stage -
BLACKLISTED_OPERATOR- Attempted to use a prohibited operator -
CROSS_DATABASE_ACCESS- Attempted to access collections in a different database -
JAVASCRIPT_EXECUTION- Attempted to execute JavaScript code
|
Important
|
These security controls are enabled by default with conservative settings to protect your data |
Transaction Support
Execute aggregations within a transaction by including the sid and txn parameters:
cURL
curl -i -X GET "[RESTHEART-URL]/mycollection/_aggrs/my-pipeline?sid=session-id&txn=transaction-id" \
-u "[BASIC-AUTH]"
HTTPie
http GET "[RESTHEART-URL]/mycollection/_aggrs/my-pipeline" \
sid==session-id txn==transaction-id \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
const params = new URLSearchParams({
sid: 'session-id',
txn: 'transaction-id'
});
fetch(`[RESTHEART-URL]/mycollection/_aggrs/my-pipeline?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Basic [BASIC-AUTH]'
}
})
.then(response => response.json())
.then(data => {
console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));
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 } }
]
}