mongodb-pipeline-builder is a powerful TypeScript library that simplifies the creation of MongoDB aggregation pipelines. It provides a fluent, type-safe API that makes your aggregation pipelines more readable, maintainable, and less error-prone.
β¨ Type-Safe - Full TypeScript support with generics for typed responses
π Readable - Fluent API that makes complex pipelines easy to understand
π§ Maintainable - Modular design with reusable helpers
π― Complete - Supports all MongoDB aggregation stages and operators
β‘ Efficient - Built-in pagination support with optimized queries
π‘οΈ Validated - Automatic validation of pipeline stages
db.collection.aggregate()db.aggregate()Model.aggregate()npm install mongodb-pipeline-builderyarn add mongodb-pipeline-builderpnpm add mongodb-pipeline-builderimport { PipelineBuilder } from 'mongodb-pipeline-builder';
import { ProjectOnlyHelper } from 'mongodb-pipeline-builder/helpers';
import { $Expression, $Equal } from 'mongodb-pipeline-builder/operators';
const pipeline = new PipelineBuilder('users-query')
.Match($Expression($Equal('$status', 'active')))
.Project(ProjectOnlyHelper('name', 'email', 'createdAt'))
.Sort({ createdAt: -1 })
.Limit(10)
.build();
// Use with MongoDB
const results = await db.collection('users').aggregate(pipeline).toArray();
// Use with Mongoose
const results = await User.aggregate(pipeline);import { PipelineBuilder, GetPagingResult } from 'mongodb-pipeline-builder';
import { $Expression, $GreaterThan } from 'mongodb-pipeline-builder/operators';
const pipeline = new PipelineBuilder('paginated-users')
.Match($Expression($GreaterThan('$age', 18)))
.Sort({ createdAt: -1 })
.Paging(20, 1) // 20 items per page, page 1
.build();
const result = await GetPagingResult(User, pipeline);
console.log(result.GetDocs()); // Document array
console.log(result.GetCount()); // Total count
console.log(result.GetTotalPageNumber()); // Total pagesimport { PipelineBuilder } from 'mongodb-pipeline-builder';
import { LookupEqualityHelper, Field } from 'mongodb-pipeline-builder/helpers';
import { $ArrayElementAt } from 'mongodb-pipeline-builder/operators';
const pipeline = new PipelineBuilder('users-with-profiles')
.Lookup(LookupEqualityHelper('profiles', 'profile', 'profileId', '_id'))
.AddFields(
Field('profileData', $ArrayElementAt('$profile', 0))
)
.Unset('profile')
.build();build() replaces getPipeline()ChangeStream, ChangeStreamSplitLargeEvent, Densify, Documents, Fill, ListLocalSessions, ListSampledQueries, ListSearchIndexes, SearchMeta, SetWindowFields, ShardedDataDistributionInsert() stage for adding custom stages without validationPayload suffix β Helper suffixLookupEqualityHelper)$ (e.g., $Add, $Match)MapOperator β $Map$Median, $Percentile, $Top, $TopN$GetField, $SetFieldGetResult<T>() - For non-paginated queriesGetElement(index | 'last') - New method to get specific documentGetPagingResult<T>() - Exclusively for paginated queriesimport { PipelineBuilder, GetResult } from 'mongodb-pipeline-builder';
import {
LookupEqualityHelper,
ProjectOnlyHelper,
Field
} from 'mongodb-pipeline-builder/helpers';
import {
$Expression,
$And,
$Equal,
$GreaterThanEqual,
$ArrayElementAt
} from 'mongodb-pipeline-builder/operators';
// Complex query: Active users with their orders
const pipeline = new PipelineBuilder('active-users-with-orders', { debug: true })
// Filter active users over 18
.Match($Expression(
$And(
$Equal('$status', 'active'),
$GreaterThanEqual('$age', 18)
)
))
// Join with orders collection
.Lookup(LookupEqualityHelper('orders', 'orders', '_id', 'userId'))
// Project specific fields
.Project(ProjectOnlyHelper('name', 'email', 'age'))
// Add computed fields
.AddFields(
Field('totalOrders', { $size: '$orders' }),
Field('lastOrder', $ArrayElementAt('$orders', -1))
)
// Sort by total orders descending
.Sort(Field('totalOrders', -1))
// Limit results
.Limit(50)
.build();
// Execute query with typed response
interface UserWithOrders {
name: string;
email: string;
age: number;
totalOrders: number;
lastOrder?: any;
}
const result = await GetResult<UserWithOrders>(User, pipeline);
// Access results
const users = result.GetDocs(); // UserWithOrders[]
const count = result.GetCount(); // number
const firstUser = result.GetElement(0); // UserWithOrders | undefined
const lastUser = result.GetElement('last'); // UserWithOrders | undefinedβ Try the library on NPM RunKit
const pipeline = [
{
$match: {
$expr: {
$and: [
{ $eq: ["$status", "active"] },
{ $gte: ["$age", 18] }
]
}
}
},
{
$lookup: {
from: "orders",
localField: "_id",
foreignField: "userId",
as: "orders"
}
},
{
$project: {
_id: 0,
name: 1,
email: 1,
age: 1,
totalOrders: { $size: "$orders" },
lastOrder: { $arrayElemAt: ["$orders", -1] }
}
},
{ $sort: { totalOrders: -1 } },
{ $limit: 50 }
];const pipeline = new PipelineBuilder('active-users')
.Match($Expression($And(
$Equal('$status', 'active'),
$GreaterThanEqual('$age', 18)
)))
.Lookup(LookupEqualityHelper('orders', 'orders', '_id', 'userId'))
.Project(
ProjectOnlyHelper('name', 'email', 'age'),
Field('totalOrders', { $size: '$orders' }),
Field('lastOrder', $ArrayElementAt('$orders', -1))
)
.Sort(Field('totalOrders', -1))
.Limit(50)
.build();Use when your pipeline does not include the Paging stage:
import { GetResult } from 'mongodb-pipeline-builder';
interface User {
name: string;
email: string;
age: number;
}
const pipeline = new PipelineBuilder('users')
.Match($Expression($Equal('$status', 'active')))
.build();
const result = await GetResult<User>(User, pipeline);
const users = result.GetDocs(); // User[] - all documents
const count = result.GetCount(); // number - total count
const firstUser = result.GetElement(0); // User | undefined
const lastUser = result.GetElement('last'); // User | undefinedUse exclusively when your pipeline includes the Paging stage:
import { GetPagingResult } from 'mongodb-pipeline-builder';
const pipeline = new PipelineBuilder('users')
.Match($Expression($Equal('$status', 'active')))
.Paging(20, 1) // Required for GetPagingResult
.build();
const result = await GetPagingResult<User>(User, pipeline);
const users = result.GetDocs(); // User[] - current page
const totalCount = result.GetCount(); // number - total documents
const totalPages = result.GetTotalPageNumber(); // number - total pagesπ Learn More: See Getting Started Guide for detailed examples
All MongoDB aggregation stages are supported. See complete reference.
Common Stages:
Match() - Filter documentsProject() - Select/transform fieldsGroup() - Group and aggregateSort() - Sort documentsLimit() - Limit resultsLookup() - Join collectionsUnwind() - Deconstruct arraysAddFields() - Add computed fieldsPaging() - Add pagination156+ MongoDB operators supported. See complete reference.
Common Operators:
$Equal, $GreaterThan, $LessThan$And, $Or, $Not$Add, $Subtract, $Multiply, $Divide$Size, $Filter, $Map, $ArrayElementAt$Concat, $ToLower, $ToUpper, $Split$DateAdd, $DateSubtract, $DateDifference$Sum, $Average, $Min, $Max, $Median, $Percentile, $Top, $TopN$GetField, $SetField, $MergeObjects20+ helper functions for common patterns. See complete list in stages.md.
Common Helpers:
Field(name, value) - Universal field helperProjectOnlyHelper(...fields) - Include specific fieldsLookupEqualityHelper(...) - Simple joinsBucketHelper(...) - Categorize documentsSearchHelper(...) - Full-text searchconst pipeline = new PipelineBuilder('active-users')
.Match($Expression($Equal('$status', 'active')))
.Project(ProjectOnlyHelper('name', 'email'))
.Sort({ createdAt: -1 })
.Limit(10)
.build();
const result = await GetResult<User>(User, pipeline);const pipeline = new PipelineBuilder('paginated')
.Match($Expression($GreaterThan('$price', 100)))
.Sort({ price: -1 })
.Paging(20, 1)
.build();
const result = await GetPagingResult<Product>(Product, pipeline);
console.log(`Page 1 of ${result.GetTotalPageNumber()}`);const pipeline = new PipelineBuilder('users-with-orders')
.Lookup(LookupEqualityHelper('orders', 'orders', '_id', 'userId'))
.AddFields(
Field('orderCount', $Size('$orders')),
Field('lastOrder', $ArrayElementAt('$orders', -1))
)
.Match($Expression($GreaterThan('$orderCount', 0)))
.build();const pipeline = new PipelineBuilder('sales-by-category')
.Match($Expression($Equal('$year', 2024)))
.Group({
_id: '$category',
totalSales: $Sum('$amount'),
averagePrice: $Average('$price'),
count: $Sum(1)
})
.Sort({ totalSales: -1 })
.Limit(10)
.build();import { $Median, $Percentile, $Top, $TopN } from 'mongodb-pipeline-builder/operators';
const pipeline = new PipelineBuilder('price-stats')
.Match($Expression($Equal('$year', 2024)))
.Group({
_id: '$category',
medianPrice: $Median('$price'),
p95Price: $Percentile('$price', [0.95]),
topProduct: $Top({ sales: -1 }, '$productName'),
top3Products: $TopN(3, { sales: -1 }, '$productName', '$price')
})
.Sort({ _id: 1 })
.build();GetResult for queries without the Paging stageGetPagingResult exclusively when using the Paging stageThe Paging stage wraps your pipeline in $facet for efficient pagination.
Yes! Use the Insert stage:
builder.Insert({ $myCustomStage: { field: 'value' } })Useful for new MongoDB stages or custom implementations.
Yes! Works with:
db.collection.aggregate()Model.aggregate()db.aggregate()Yes! All stages through MongoDB 7.0+ including $densify, $fill, $setWindowFields, $searchMeta, and more.
MongoDB 7.0+ introduced several statistical operators:
$Median - Calculates the median of values (MongoDB 7.2+)$Percentile - Calculates percentiles (MongoDB 7.2+)$Top - Returns the top element in a group (MongoDB 7.0+)$TopN - Returns the top N elements in a group (MongoDB 7.0+)$GetField - Retrieves a field value dynamically (MongoDB 5.0+)$SetField - Sets a field value dynamically (MongoDB 5.0+)See Example 5 for usage.
Use generics:
interface User { name: string; email: string; }
const result = await GetResult<User>(User, pipeline);
const users: User[] = result.GetDocs(); // Fully typed!Yes! Chain as many as needed:
builder
.Lookup(LookupEqualityHelper('profiles', 'profile', '_id', 'userId'))
.Lookup(LookupEqualityHelper('orders', 'orders', '_id', 'userId'))
.Lookup(LookupEqualityHelper('subscriptions', 'sub', '_id', 'userId'))See Lookup Examples.
$match and $sort fields$match as early as possibleπ Learn More: Pagination Examples
Include:
See CHANGELOG.md for version history.
Made with β€οΈ by MickaΓ«l NODANCHE