A robust, type-safe implementation of the Criteria Pattern for MongoDB queries in TypeScript. Build complex database queries dynamically with a fluent, composable API designed following Domain-Driven Design (DDD) and Clean Architecture principles.
- π― Overview
- β¨ Key Features
- π¦ Installation
- π Quick Start
- ποΈ Migrations
- π Documentation
- π§ͺ Testing
- π€ Contributing
- π License
The Criteria Pattern is a powerful design pattern that enables dynamic query construction without writing raw database queries. This library provides a type-safe, MongoDB-specific implementation that helps you:
- Build queries dynamically based on runtime conditions
- Maintain type safety throughout your query construction
- Compose and reuse query components across your application
- Separate concerns between query logic and data models
- Test queries easily with a mockable interface
The Criteria pattern encapsulates query logic in a structured, object-oriented way. Instead of building query strings or objects directly, you compose Criteria objects that represent your search intentions. This approach provides flexibility, reusability, testability, and type safety.
- Full TypeScript support with strict typing
- Compile-time validation of query structure
- IntelliSense support for all operations
- Support for all common MongoDB operators (EQUAL, NOT_EQUAL, GT, GTE, LT, LTE, CONTAINS, NOT_CONTAINS)
- NEW: OR operator for complex logical combinations
- Composable filters that can be combined and reused
- Dynamic query construction based on runtime conditions
// Simple equality
{ status: { $eq: "active" } }
// Complex OR conditions
{ $or: [
{ name: { $regex: "john" } },
{ email: { $regex: "john" } }
]}
// Range queries
{ age: { $gte: 18 }, price: { $lte: 999.99 } }- Native MongoDB 6.0+ support
- Efficient query generation
- Automatic index-friendly query structure
- Only peer dependencies (MongoDB driver)
- Lightweight bundle size
- Repository pattern implementation
- Domain-driven design principles
- Separation of concerns
# Using npm
npm install @abejarano/ts-mongodb-criteria
# Using yarn
yarn add @abejarano/ts-mongodb-criteria
# Using pnpm
pnpm add @abejarano/ts-mongodb-criteriaThis repo is Bun-first and ships with a bun.lock. Use Bun for installs and
scripts:
bun install# MongoDB driver (required)
npm install mongodb@^6.0.0
# TypeScript (for development)
npm install -D typescript@^5.0.0- Node.js: 20.0.0 or higher
- TypeScript: 5.0.0 or higher (for development)
- MongoDB: 6.0.0 or higher
import {
Criteria,
Filters,
Order,
Operator,
MongoRepository,
} from "@abejarano/ts-mongodb-criteria"
// 1. Create filters using a simple Map-based syntax
const filters = [
new Map([
["field", "status"],
["operator", Operator.EQUAL],
["value", "active"],
]),
new Map([
["field", "age"],
["operator", Operator.GTE],
["value", "18"],
]),
]
// 2. Build criteria with filters, sorting, and pagination
const criteria = new Criteria(
Filters.fromValues(filters),
Order.desc("createdAt"),
20, // limit
1 // page
)
// 3. Use with your MongoDB repository
class UserRepository extends MongoRepository<User> {
constructor() {
super(User)
}
collectionName(): string {
return "users"
}
// Create indexes the first time the collection is accessed
protected async ensureIndexes(collection: Collection): Promise<void> {
await collection.createIndex({ email: 1 }, { unique: true })
}
}
const userRepo = new UserRepository()
const { results } = await userRepo.list(criteria)Indexes are created lazily the first time the repository accesses the collection
(via list, one, or upsert).
Use the collection argument inside ensureIndexes to avoid recursion.
MongoRepository provides ready-to-use public methods for repositories that extend it:
list(criteria, transaction?)for paginated queriesmany(filter, options?)to fetch multiple entities matching a filter, with optional{ transaction?, sort? }one(filter, transaction?)to fetch a single entityupsert(entity, transaction?)to persist an aggregate Internal helpers are private, so repositories should call these public methods directly.
If you need a repository interface in your app, extend IRepository<T> so your
custom interfaces stay aligned with the library return types:
import type { IRepository } from "@abejarano/ts-mongodb-criteria"
export interface IUserRepository extends IRepository<User> {
// Add domain-specific methods here
}The goal of IRepository is to prevent signature drift between your app's
interfaces and the library's repository return types.
MongoTransaction.run groups writes from one or more repositories into a
single MongoDB transaction. Pass the tx context to every write that must be
committed or rolled back together:
import { MongoTransaction } from "@abejarano/ts-mongodb-criteria"
await MongoTransaction.run(async (tx) => {
await userRepository.upsert(user, tx)
await auditRepository.upsert(auditEntry, tx)
await sessionRepository.delete({ userId: user.getId() }, undefined, tx)
// Reads can observe writes made earlier in this transaction.
const persistedUser = await userRepository.one({ id: user.getId() }, tx)
const activeUsers = await userRepository.many(
{ status: "active" },
{ transaction: tx, sort: Order.asc("name") }
)
})If any operation in the callback fails, MongoDB rolls back all the writes and
the error is propagated to the caller. The MongoDB driver handles retryable
transaction errors through withTransaction.
Custom repositories can include their protected atomic updates in the same
transaction by accepting and forwarding MongoTransaction:
async deactivateUser(id: string, tx: MongoTransaction): Promise<void> {
await this.updateOne({ id }, { $set: { active: false } }, tx)
}MongoDB transactions require a replica set or a sharded cluster; standalone
MongoDB instances do not support them. This initial API applies the transaction
context to upsert, delete, protected updateOne, one, list, and many.
Pass tx as the second argument to list, and inside the options object ({ transaction: tx }) to many.
Your First Query in 30 Seconds:
// Find active users over 18, sorted by creation date
const activeAdultUsers = new Criteria(
Filters.fromValues([
new Map([
["field", "status"],
["operator", Operator.EQUAL],
["value", "active"],
]),
new Map([
["field", "age"],
["operator", Operator.GTE],
["value", "18"],
]),
]),
Order.desc("createdAt"),
10, // Get 10 results
1 // First page
)
const results = await repository.list(activeAdultUsers)This repo includes a CLI wrapper for migrate-mongo.
To use it in your application:
- Initialize the configuration:
bun ts-mongo init-
Edit
migrate-mongo-config.jswith your database details. -
Run migrations using the
ts-mongocommand:
# Create a new migration
bun ts-mongo migrate:create add-users-index
# Run migrations up
bun ts-mongo migrate:up
# Undo last migration
bun ts-mongo migrate:down
# Check status
bun ts-mongo migrate:statusNote: You can also use npx, bunx or yarn run.
See the full CLI guide here: docs/mongo-migrations.md.
- π Quick Start Guide - Get up and running in minutes
- ποΈ Criteria Pattern Guide - Deep dive into the pattern, architecture, and theory
- π§ Operators Reference - Complete guide to all available operators and their usage
- β‘ Performance Guide - Optimization strategies and best practices
- π Migration Guide - Migrate from other query systems to Criteria pattern
- ποΈ MongoDB Migrations (CLI) - Run database migrations with migrate-mongo
const criteria = new Criteria(filters, order, limit?, page?)const filters = Filters.fromValues([...filterMaps])const order = Order.desc("createdAt") // or Order.asc("name")EQUAL,NOT_EQUAL- Exact matchingGT,GTE,LT,LTE- Range operationsBETWEEN- Inclusive range with lower and upper boundsCONTAINS,NOT_CONTAINS- Text searchOR- Logical OR combinations
This library ships dual builds (CJS + ESM) and works in both Node.js and Bun.
import { OrCondition } from "@abejarano/ts-mongodb-criteria"
// Search across multiple fields
const searchConditions: OrCondition[] = [
{ field: "name", operator: Operator.CONTAINS, value: "john" },
{ field: "email", operator: Operator.CONTAINS, value: "john" },
]
const filters = [
new Map([
["field", "search"],
["operator", Operator.OR],
["value", searchConditions],
]),
]
// Generates: { $or: [
// { name: { $regex: "john" } },
// { email: { $regex: "john" } }
// ]}// Filter users created between two dates
const filters = [
new Map([
["field", "createdAt"],
["operator", Operator.BETWEEN],
["value", { start: new Date("2024-01-01"), end: new Date("2024-01-31") }],
]),
]
const criteria = new Criteria(Filters.fromValues(filters), Order.none())
// Generates: { createdAt: { $gte: 2024-01-01, $lte: 2024-01-31 } }The library includes comprehensive test coverage (45/45 tests passing).
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverageWe welcome contributions! Please follow these guidelines:
# Clone the repository
git clone https://github.com/abejarano/ts-mongo-criteria.git
cd ts-mongo-criteria
# Install dependencies
yarn install
# Run tests
yarn test
# Build the project
yarn build- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
yarn test) - Commit using conventional commits (
git commit -m 'feat: add amazing feature') - Push to your branch (
git push origin feature/amazing-feature) - Open a Pull Request
To use the migration features, you must first initialize the configuration in your project:
bun run ts-mongo initThis will create a migrate-mongo-config.js file in your project root. You must configure your MongoDB connection settings in this file (or via environment variables as defined in the config).
| Command | Description |
|---|---|
init |
Initialize configuration file |
migrate:create <name> |
Create a new migration file |
migrate:up |
Run pending migrations |
migrate:down |
Revert the last applied migration |
migrate:status |
Check the status of migrations |
Example:
bun run ts-mongo migrate:create add-users-collectionThis project is licensed under the MIT License. See the LICENSE file for details.
Γngel Bejarano
π§ angel.bejarano@jaspesoft.com
π GitHub
π’ Jaspesoft
βοΈ If this project helps you, please give it a star on GitHub!
π€ Questions or suggestions? Open an issue or start a discussion.
π’ Follow us for updates and new features!