This repository provides a robust, feature-rich, and secure solution for building, customizing, and optimizing your Node.js APIs powered by MongoDB.
این مخزن راهکاری قدرتمند، جامع و امن برای ساخت، شخصیسازی و بهینهسازی APIهای Node.js مبتنی بر MongoDB ارائه میدهد.
- Installation & Setup / نصب و راهاندازی
- Overview / معرفی
- API Features Class Methods / متدهای کلاس API Features
- Input Types & Supported Operators / انواع ورودی و اپراتورهای پشتیبانیشده
- Additional Conditions / شرایط اضافی
- Security Configuration / تنظیمات امنیتی
- Security & Performance Enhancements / بهبودهای امنیتی و عملکردی
- Error Handling Middleware / مدیریت خطا به صورت Middleware
- Full Examples / مثالهای کامل
- Summary / جمعبندی
- Prerequisites: Node.js 16+, MongoDB 5+, Mongoose 7+
- Install Dependencies:
For testing purposes, install Jest or Mocha:
npm install mongoose lodash dotenv winston
npm install --save-dev jest
The ApiFeatures class processes incoming query parameters and builds an aggregation pipeline step by step. It supports:
- Advanced filtering, sorting, and field selection.
- Pagination with default values (defaults to page 1 and limit 10 if not provided).
- Population of related documents including support for nested population.
- Automatic application of conditions such as
isActive: trueif the field exists (for non-admin users). - Input sanitization, numeric validation, and security enforcement (via
securityConfig). - Enhanced logging using winston and improved error handling with custom error classes.
- Dynamic configuration: Security settings are maintained in a separate config file.
- Performance improvements: Includes aggregation cursor support and optimized pipeline order.
- Error middleware: A centralized error handling middleware (
catchError) is provided.
- Description:
Parses query parameters, merges them with manually added filters (if provided), and applies security filters. If the model schema includes anisActivefield and the user is not "admin," it auto-addsisActive: true. - Usage Example:
// URL: /api/products?status=active&price[gte]=100 const features = new ApiFeatures(Product, req.query); features.filter(); // Pipeline adds: { $match: { status: "active", price: { $gte: 100 }, isActive: true } }
- Description:
Converts a comma-separated list of sorting fields into a$sortobject. A "-" before a field indicates descending order. - Usage Example:
// URL: /api/products?sort=-price,createdAt const features = new ApiFeatures(Product, req.query); features.sort(); // Pipeline adds: { $sort: { price: -1, createdAt: 1 } }
- Description:
Uses$projectto return only the specified fields, excluding forbidden fields (e.g., "password"). - Usage Example:
// URL: /api/products?fields=name,price,category,password const features = new ApiFeatures(Product, req.query); features.limitFields(); // Pipeline adds: { $project: { name: 1, price: 1, category: 1 } }
- Description:
Applies pagination by determining the page and limit. Defaults to page 1 and limit 10 if not provided. The max limit is based on the user's role (configured insecurityConfig). - Usage Example:
// URL: /api/products?page=2&limit=20 const features = new ApiFeatures(Product, req.query, "user"); features.paginate(); // Pipeline adds: { $skip: 20 } and { $limit: 20 }
- Description:
Joins related documents using$lookupand$unwind. It supports:- String Input: A comma-separated list of field names.
- Object Input: An object with
path(required) andselect(optional). - Array Input: An array of strings or objects for multiple or nested populates.
- Usage Examples:
- String Input:
// URL: /api/products?populate=category,brand const features = new ApiFeatures(Product, req.query); features.populate(); // Adds lookup for "category" and "brand"
- Object Input:
const populateOptions = { path: "category", select: "name description" }; const features = new ApiFeatures(Product, req.query); features.populate(populateOptions);
- Array Input:
const populateArray = [ "brand", // simple string { path: "category", select: "name description" }, { path: "category", select: "name", populate: { path: "subCategory", select: "title" } } ]; const features = new ApiFeatures(Product, req.query, "admin"); features.populate(populateArray);
- String Input:
- Description:
Merges additional filters with those parsed from the query. Note: CalladdManualFilters()beforefilter()so that manual filters are included. - Usage Example:
const manualFilter = { category: "electronics" }; const features = new ApiFeatures(Product, { status: "active" }); features.addManualFilters(manualFilter).filter();
- Description:
Executes the aggregation pipeline using Mongoose and returns an object containing a success flag, total count, and result data. Supports aggregation cursor for large datasets. - Usage Example:
const features = new ApiFeatures(Product, req.query); const result = await features .filter() .sort() .limitFields() .paginate() .populate() .execute(); console.log(result);
The class converts query parameters into MongoDB operators:
| Operator | Example Query | Description |
|---|---|---|
| eq | ?age=25 |
Equal to |
| ne | ?status[ne]=inactive |
Not equal to |
| gt | ?price[gt]=100 |
Greater than |
| gte | ?stock[gte]=50 |
Greater than or equal to |
| lt | ?weight[lt]=500 |
Less than |
| lte | ?rating[lte]=3 |
Less than or equal to |
| in | ?colors[in]=red,blue |
In the list |
| nin | ?size[nin]=xl |
Not in the list |
| regex | ?name[regex]=^A |
Regex search |
| exists | ?discount[exists]=true |
Field existence |
- Sorting: Uses
$sort - Projection: Uses
$project - Pagination: Uses
$skipand$limit(defaults to page 1 and limit 10)
- String: e.g.,
"category,brand" - Object: e.g.,
{ path: "category", select: "name description" } - Array: e.g.,
["brand", { path: "category", select: "name" }, ...]
- isActive Condition:
If the model includes anisActivefield and the user is not admin,filter()auto-addsisActive: true. - Default Pagination:
If nopageorlimitare provided, defaults are used (page 1, limit 10), adjusted based on user role. - Numeric Validation:
Validates that fields such aspageandlimitcontain only numbers. - Removal of Dangerous Operators:
Operators like$where,$accumulator, and$functionare removed from the query. - Ordering of Manual Filters:
CalladdManualFilters()beforefilter()to ensure manual filters are properly merged.
Security settings are used to enforce:
- Allowed operators
- Forbidden fields (e.g.,
"password") - Role-based access limits (maxLimit and allowedPopulate)
export const securityConfig = {
allowedOperators: [
"eq", "ne", "gt", "gte", "lt", "lte", "in", "nin", "regex", "exists", "size", "or", "and"
],
forbiddenFields: ["password"],
accessLevels: {
guest: { maxLimit: 50, allowedPopulate: ["*"] },
user: { maxLimit: 100, allowedPopulate: ["*"] },
admin: { maxLimit: 1000, allowedPopulate: ["*"] },
superAdmin: { maxLimit: 1000, allowedPopulate: ["*"] }
}
};These settings are automatically applied in methods like filter(), paginate(), and limitFields().
- Enhanced Logging:
Uses winston to log events and errors with different levels (info, warn, error) along with timestamps and stack traces. - Improved Error Handling:
Centralized error handling is done with a custom error class (HandleERROR) that marks errors as operational. - Dynamic Configuration:
Security settings are maintained inconfig.js, allowing runtime changes without modifying core code. - Performance Optimization:
Aggregation cursor support is added inexecute()for large datasets, and pipelines are optimized by placing filtering stages at the beginning.
A dedicated error-handling middleware (catchError) is implemented to centralize error responses:
import catchError from "./errorHandler.js";
// Usage in Express.js:
// app.use(catchError);This middleware standardizes error responses by setting the HTTP status code and JSON error message.
import ApiFeatures from "./api-features.js";
import Product from "./models/product.js";
// URL: /api/products?status=active&price[gte]=100&sort=-price,createdAt&fields=name,price,category&page=1&limit=10&populate=category,brand
const features = new ApiFeatures(Product, req.query, "user");
const result = await features
.filter()
.sort()
.limitFields()
.paginate()
.populate()
.execute();
console.log(result);(Call addManualFilters() before filter())
const query = { status: "active" };
const manualFilter = { category: "electronics" };
const features = new ApiFeatures(Product, query, "user");
features.addManualFilters(manualFilter).filter();
const result = await features.execute();
console.log(result);const populateArray = [
"brand", // Simple string input
{ // Object input with projection
path: "category",
select: "name description"
},
{ // Nested populate: populate "category" then its "subCategory"
path: "category",
select: "name",
populate: { path: "subCategory", select: "title" }
}
];
const features = new ApiFeatures(Product, req.query, "admin");
const result = await features.populate(populateArray).execute();
console.log(result);GET /api/products?
page=1&
limit=10&
sort=-createdAt,price&
fields=name,price,category&
populate=category,brand&
price[gte]=1000&
category[in]=electronics,phones&
name[regex]=^Samsung// URL: /api/products?status=active
const features = new ApiFeatures(Product, req.query);
const result = await features
.filter() // will use default page = 1, limit = 10
.execute();
console.log(result);- Filtering:
Uses$matchto apply combined query filters, manual filters, and conditionisActive: true(for non-admin users). - Sorting:
Converts a comma-separated string into a$sortobject. - Field Selection:
Uses$projectto return only permitted fields. - Pagination:
Applies$skipand$limit(defaults to page 1 and limit 10 if parameters are missing) with role-based max limits. - Populate:
Uses$lookupand$unwindto join related documents; supports string, object, and array inputs (including nested population). - Security:
Enforces allowed operators, sanitizes inputs, validates numeric fields, and removes dangerous operators throughsecurityConfig. - Ordering of Manual Filters:
EnsureaddManualFilters()is called beforefilter()for proper inclusion. - Enhanced Logging & Error Handling:
Advanced logging via winston and centralized error handling using a custom error class (HandleERROR) along with an Express middleware (catchError). - Performance Optimizations:
Supports aggregation cursor for large datasets and optimizes aggregation pipelines for improved resource usage.
This comprehensive class provides a one-stop solution for integrating powerful, secure, and customizable query capabilities into any Node.js/MongoDB project.
- پیشنیازها: Node.js 16+، MongoDB 5+، Mongoose 7+
- نصب کتابخانهها:
npm install mongoose lodash dotenv winston
کلاس ApiFeatures پارامترهای ورودی را پردازش کرده و به صورت مرحله به مرحله یک Pipeline Aggregation میسازد. این کلاس از فیلترینگ پیشرفته، مرتبسازی، انتخاب فیلد، صفحهبندی و پرکردن اسناد مرتبط پشتیبانی میکند. همچنین شرایطی مانند افزودن خودکار شرط isActive: true (برای کاربران غیر admin) و استفاده از مقادیر پیشفرض صفحهبندی (صفحه 1 و تعداد 10 آیتم) را اعمال میکند. علاوه بر این، با استفاده از لاگینگ پیشرفته (winston)، مدیریت خطاهای متمرکز (با کلاس HandleERROR و middleware catchError) و پیکربندی داینامیک تنظیمات امنیتی، بهینهسازی عملکرد نیز انجام شده است.
- توضیح:
پارامترهای کوئری را تجزیه کرده، آنها را با فیلترهای دستی ترکیب کرده و سپس فیلترهای امنیتی را اعمال میکند. اگر فیلدisActiveوجود داشته باشد و کاربر admin نباشد، شرطisActive: trueاضافه میشود. - مثال استفاده:
// URL: /api/products?status=active&price[gte]=100 const features = new ApiFeatures(Product, req.query); features.filter(); // به Pipeline اضافه میکند: { $match: { status: "active", price: { $gte: 100 }, isActive: true } }
- توضیح:
رشتهای از فیلدهای مرتبسازی را دریافت کرده و به شیء$sortتبدیل میکند؛ اگر فیلد با "-" شروع شود، ترتیب نزولی در نظر گرفته میشود. - مثال استفاده:
// URL: /api/products?sort=-price,createdAt const features = new ApiFeatures(Product, req.query); features.sort(); // به Pipeline اضافه میکند: { $sort: { price: -1, createdAt: 1 } }
- توضیح:
از$projectاستفاده میکند تا تنها فیلدهای مجاز را برگرداند و فیلدهای ممنوع مانندpasswordحذف شوند. - مثال استفاده:
// URL: /api/products?fields=name,price,category,password const features = new ApiFeatures(Product, req.query); features.limitFields(); // به Pipeline اضافه میکند: { $project: { name: 1, price: 1, category: 1 } }
- توضیح:
با استفاده از$skipو$limit، صفحهبندی را براساس پارامترهای ورودی یا مقادیر پیشفرض (صفحه 1، تعداد 10 آیتم) اعمال میکند. محدودیت تعداد آیتم نیز بر اساس نقش کاربر تنظیم میشود. - مثال استفاده:
// URL: /api/products?page=2&limit=20 const features = new ApiFeatures(Product, req.query, "user"); features.paginate(); // به Pipeline اضافه میکند: { $skip: 20 } و { $limit: 20 }
- توضیح:
با استفاده از$lookupو$unwind، اسناد مرتبط را به سند اصلی متصل میکند. این متد از ورودیهای مختلف مانند رشتهای، شیئی و آرایهای (حتی تو در تو) پشتیبانی میکند. - مثالهای استفاده:
- ورودی رشتهای:
// URL: /api/products?populate=category,brand const features = new ApiFeatures(Product, req.query); features.populate();
- ورودی شیئی:
const populateOptions = { path: "category", select: "name description" }; const features = new ApiFeatures(Product, req.query); features.populate(populateOptions);
- ورودی آرایهای:
const populateArray = [ "brand", { path: "category", select: "name description" }, { path: "category", select: "name", populate: { path: "subCategory", select: "title" } } ]; const features = new ApiFeatures(Product, req.query, "admin"); features.populate(populateArray);
- ورودی رشتهای:
- توضیح:
فیلترهای دستی اضافهشده را با فیلترهای استخراج شده از کوئری ترکیب میکند. توجه: ابتدا این متد را فراخوانی کنید و سپس متد filter() را اجرا نمایید. - مثال استفاده:
const manualFilter = { category: "electronics" }; const features = new ApiFeatures(Product, { status: "active" }); features.addManualFilters(manualFilter).filter();
- توضیح:
Pipeline ساختهشده را با استفاده از Mongoose اجرا میکند و نتیجه شامل تعداد کل و دادههای استخراجشده را برمیگرداند. در صورت نیاز از aggregation cursor جهت پردازش بهینه دادههای حجیم استفاده میکند. - مثال استفاده:
const features = new ApiFeatures(Product, req.query); const result = await features .filter() .sort() .limitFields() .paginate() .populate() .execute(); console.log(result);
- اپراتورهای فیلترینگ: مانند eq، ne، gt، gte، lt، lte، in، nin، regex و exists.
- مرتبسازی، انتخاب فیلد و صفحهبندی: با استفاده از
$sort،$project،$skipو$limit. - ورودی Populate: ورودی به صورت رشتهای (به عنوان مثال "category,brand")، شیئی یا آرایهای امکانپذیر است.
- شرط isActive: در صورت وجود فیلد
isActiveدر اسکیما و عدم وجود نقش admin، شرطisActive: trueبه کوئری اضافه میشود. - صفحهبندی پیشفرض: در صورت عدم ارسال
pageوlimitاز مقادیر پیشفرض (صفحه 1، تعداد 10) استفاده میشود. - اعتبارسنجی ورودی: فیلدهای عددی مانند page و limit اعتبارسنجی میشوند تا تنها شامل اعداد باشند.
- حذف اپراتورهای خطرناک: اپراتورهایی مانند
$where،$accumulatorو$functionاز ورودی حذف میشوند. - ترتیب استفاده از فیلترهای دستی: ابتدا
addManualFilters()فراخوانی و سپسfilter()اجرا میشود.
تنظیمات امنیتی شامل محدودیتهای اپراتور، حذف فیلدهای ممنوع و محدودیتهای مبتنی بر نقش است:
export const securityConfig = {
allowedOperators: [
"eq", "ne", "gt", "gte", "lt", "lte", "in", "nin", "regex", "exists", "size", "or", "and"
],
forbiddenFields: ["password"],
accessLevels: {
guest: { maxLimit: 50, allowedPopulate: ["*"] },
user: { maxLimit: 100, allowedPopulate: ["*"] },
admin: { maxLimit: 1000, allowedPopulate: ["*"] },
superAdmin: { maxLimit: 1000, allowedPopulate: ["*"] }
}
};این تنظیمات بهصورت خودکار در متدهایی مانند filter، paginate و limitFields اعمال میشوند.
- لاگینگ پیشرفته:
با استفاده از winston، رویدادها و خطاها با سطوح مختلف (info, warn, error) و اطلاعات دقیق (timestamp و stack trace) ثبت میشوند. - مدیریت خطا بهبود یافته:
استفاده از کلاس سفارشی HandleERROR جهت دستهبندی خطاها و middleware استاندارد جهت پاسخدهی به خطاها. - تنظیمات داینامیک:
تنظیمات امنیتی در فایل config.js نگهداری شده و به راحتی قابل تغییر بدون نیاز به تغییر کد اصلی میباشد. - بهینهسازی عملکرد:
پشتیبانی از aggregation cursor در متد execute جهت پردازش دادههای حجیم و بهینهسازی ترتیب مراحل Pipeline جهت کاهش مصرف منابع.
برای مدیریت یکنواخت خطاهای رخ داده در API از middleware زیر استفاده میشود:
import catchError from "./errorHandler.js";
// استفاده در Express.js:
app.use(catchError);این middleware، خطا را دریافت کرده و با تنظیم status و پیام مناسب، پاسخ استانداردی به کلاینت برمیگرداند.
import ApiFeatures from "./api-features.js";
import Product from "./models/product.js";
// URL: /api/products?status=active&price[gte]=100&sort=-price,createdAt&fields=name,price,category&page=1&limit=10&populate=category,brand
const features = new ApiFeatures(Product, req.query, "user");
const result = await features
.filter()
.sort()
.limitFields()
.paginate()
.populate()
.execute();
console.log(result);(ابتدا addManualFilters() سپس filter() فراخوانی شود)
const query = { status: "active" };
const manualFilter = { category: "electronics" };
const features = new ApiFeatures(Product, query, "user");
features.addManualFilters(manualFilter).filter();
const result = await features.execute();
console.log(result);const populateArray = [
"brand", // ورودی رشتهای ساده
{ // ورودی شیئی با انتخاب فیلد
path: "category",
select: "name description"
},
{ // پرکردن تو در تو: "category" به همراه "subCategory"
path: "category",
select: "name",
populate: { path: "subCategory", select: "title" }
}
];
const features = new ApiFeatures(Product, req.query, "admin");
const result = await features.populate(populateArray).execute();
console.log(result);GET /api/products?
page=1&
limit=10&
sort=-createdAt,price&
fields=name,price,category&
populate=category,brand&
price[gte]=1000&
category[in]=electronics,phones&
name[regex]=^Samsung// URL: /api/products?status=active
const features = new ApiFeatures(Product, req.query);
const result = await features
.filter() // در صورت عدم ارسال page/limit، صفحه 1 و محدودیت 10 اعمال میشود
.execute();
console.log(result);- فیلترینگ: از
$matchبرای ترکیب فیلترهای کوئری، فیلترهای دستی و شرطisActive: true(در صورت وجود) استفاده میشود. - مرتبسازی: رشتههای جداشده با کاما به یک شیء
$sortتبدیل میشوند. - انتخاب فیلد: با استفاده از
$projectتنها فیلدهای مجاز برگردانده میشوند. - صفحهبندی: از
$skipو$limitبا مقادیر پیشفرض (صفحه 1، محدودیت 10) استفاده میشود؛ محدودیت بر اساس نقش کاربر تنظیم میشود. - پرکردن: با استفاده از
$lookupو$unwindاسناد مرتبط به یک سند متصل میشوند؛ ورودیهای رشتهای، شیئی و آرایهای (شامل پرکردن تو در تو) پشتیبانی میشوند. - امنیت: اپراتورهای مجاز، پاکسازی ورودی، اعتبارسنجی عددی، حذف فیلدهای ممنوع و محدودیتهای مبتنی بر نقش از طریق تنظیمات securityConfig اعمال میشوند.
- بهبود عملکرد: پشتیبانی از aggregation cursor و بهینهسازی ترتیب مراحل Pipeline.
- لاگینگ و مدیریت خطا: استفاده از winston برای لاگینگ و کلاس سفارشی HandleERROR به همراه middleware catchError جهت مدیریت یکنواخت خطاها.
- تنظیمات داینامیک: امکان تغییر تنظیمات امنیتی از طریق فایل config.js بدون نیاز به تغییرات در کد اصلی.
این کلاس و ساختار پروژه یک راهکار جامع جهت ادغام قابلیتهای پرسوجو قدرتمند، امنیت بالا، بهینهسازی عملکرد و نگهداری آسان در هر پروژه Node.js/MongoDB فراهم میکند.