vanta-api 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +264 -191
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,290 +1,363 @@
1
- # VantaApi: Advanced MongoDB API Utilities
2
-
3
- ![npm](https://img.shields.io/npm/v/vanta-api) ![license](https://img.shields.io/github/license/yourusername/vanta-api) ![downloads](https://img.shields.io/npm/dm/vanta-api)
1
+ # VantaApi :: Advanced MongoDB API Utilities
4
2
 
5
3
  **VantaApi** is a comprehensive toolkit for building secure, performant, and flexible APIs on top of MongoDB with Mongoose. It streamlines common query operations—filtering, sorting, field selection, pagination, and population—while enforcing robust security policies and sanitization.
6
4
 
7
5
  ---
8
6
 
9
- ## 🧩 Features Overview
10
-
11
- * **Advanced Query Parsing**: Convert HTTP query parameters into MongoDB aggregation stages.
12
- * **Filtering**: Support for comparison operators (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`), inclusion (`in`, `nin`), regex, existence, logical (`or`, `and`), and nested filters.
13
- * **Sorting**: Dynamic, multi-field sorting with ascending/descending options and schema-based validation.
14
- * **Field Selection**: Whitelisted projection of fields, automatic exclusion of sensitive data.
15
- * **Pagination**: `$skip`/`$limit` with default values, role-based maximum limits, and helpful HTTP headers.
16
- * **Population**: Flexible population via `$lookup` and `$unwind`, supporting string, object, array inputs, deep (nested) references, and role-based permissions.
17
- * **Security & Sanitization**: Whitelist operators, strip dangerous operators (`$where`, `$function`, etc.), sanitize ObjectId and date values, and enforce forbidden fields.
18
- * **Role-Based Access Control**: Define per-role limits on pagination, allowed populate paths, and other behaviors in a central config.
19
- * **Performance Safeguards**: Limit maximum pipeline stages, enforce `maxTimeMS` for aggregation, and optional aggregation cursor support for large datasets.
20
- * **Logging & Debugging**: Integrate with Winston for structured logs, debug flag to print built pipelines.
21
- * **Error Handling**: Custom error class (`HandleERROR`), `catchAsync` helper, and `catchError` middleware for uniform error responses.
22
- * **TypeScript-Ready**: Designed with generics and typings in mind for seamless TS integration.
23
-
24
- ---
25
-
26
- ## 🚀 Installation
27
-
28
- ### Requirements
29
-
30
- * **Node.js** 16 or higher
31
- * **MongoDB** 5 or higher
32
- * **Mongoose** 7 or higher
7
+ ## 📦 Installation
33
8
 
34
- ### Installation
9
+ Install via npm or Yarn:
35
10
 
36
11
  ```bash
37
12
  npm install vanta-api
38
- ```
39
-
40
- or
41
-
42
- ```bash
13
+ # or
43
14
  yarn add vanta-api
44
15
  ```
45
16
 
46
- Also install peer dependencies in your project:
47
-
48
- ```bash
49
- npm install mongoose winston pluralize
50
- ```
17
+ A `postinstall` hook will scaffold a `security-config.js` file in your project root.
51
18
 
52
19
  ---
53
20
 
54
- ## 🔧 Configuration
21
+ ## ⚙️ Setup & Initialization
55
22
 
56
- Centralize security and behavior settings in `config.js`:
23
+ ### 1. Importing the Package
57
24
 
58
- ```js
59
- export const securityConfig = {
60
- // Allowed filter operators
61
- allowedOperators: [
62
- 'eq','ne','gt','gte','lt','lte','in','nin','regex','exists','size','or','and'
63
- ],
25
+ #### ECMAScript Module (ESM)
64
26
 
65
- // Fields never exposed or matched
66
- forbiddenFields: ['password','__v'],
67
-
68
- // Role-based settings
69
- accessLevels: {
70
- guest: { maxLimit: 50, allowedPopulate: ['*'] },
71
- user: { maxLimit: 100, allowedPopulate: ['profile','orders'] },
72
- admin: { maxLimit: 1000, allowedPopulate: ['*'] },
73
- superAdmin: { maxLimit: 5000, allowedPopulate: ['*'] }
74
- },
75
-
76
- // Aggregation safeguards
77
- maxPipelineStages: 20
78
- };
27
+ ```js
28
+ import express from 'express';
29
+ import mongoose from 'mongoose';
30
+ import ApiFeatures, { catchAsync, catchError, HandleERROR } from 'vanta-api';
79
31
  ```
80
32
 
81
- * **allowedOperators**: Only these operators pass through parsing.
82
- * **forbiddenFields**: Always removed from `$match` and `$project`.
83
- * **accessLevels**: Configure per-role `maxLimit` and `allowedPopulate` paths.
84
- * **maxPipelineStages**: Prevent overly complex pipelines.
85
-
86
- ---
87
-
88
- ## 🛠️ Usage Guide
89
-
90
- ### 1. Importing
33
+ #### CommonJS (CJS)
91
34
 
92
35
  ```js
93
- import ApiFeatures, { HandleERROR, catchAsync, catchError } from 'vanta-api';
36
+ const express = require('express');
37
+ const mongoose = require('mongoose');
38
+ const { default: ApiFeatures, catchAsync, catchError, HandleERROR } = require('vanta-api');
94
39
  ```
95
40
 
96
- ### 2. Express Integration
41
+ ### 2. Basic Server Setup
97
42
 
98
43
  ```js
99
- import express from 'express';
100
- import Product from './models/product.js';
44
+ const app = express();
45
+ app.use(express.json());
46
+
47
+ // Connect to MongoDB
48
+ mongoose.connect(process.env.MONGO_URI, {
49
+ useNewUrlParser: true,
50
+ useUnifiedTopology: true
51
+ });
52
+ ```
101
53
 
102
- const router = express.Router();
54
+ ### 3. Route Definition & Error Handling
103
55
 
104
- router.get(
105
- '/',
106
- catchAsync(async (req, res) => {
107
- const features = new ApiFeatures(Product, req.query, req.user.role);
108
- const result = await features
56
+ ```js
57
+ // Example route with async handler
58
+ app.get(
59
+ '/api/v1/items',
60
+ catchAsync(async (req, res, next) => {
61
+ const result = await new ApiFeatures(ItemModel, req.query, req.user.role)
109
62
  .filter()
110
63
  .sort()
111
64
  .limitFields()
112
65
  .paginate()
113
66
  .populate()
114
- .execute({ debug: req.query.debug === 'true' });
115
-
116
- res
117
- .set('X-Total-Count', result.count)
118
- .status(200)
119
- .json(result);
67
+ .execute();
68
+ res.status(200).json(result);
120
69
  })
121
70
  );
122
71
 
123
- // Global error handler
72
+ // Global error handler (after all routes)
124
73
  app.use(catchError);
74
+
75
+ // Start server
76
+ app.listen(3000, () => {
77
+ console.log('Server running on port 3000');
78
+ });
125
79
  ```
126
80
 
127
- ### 3. Chaining Methods
81
+ ---
128
82
 
129
- All methods (except `addManualFilters`) are chainable and return `this`.
83
+ ## 🔍 API Reference
130
84
 
131
- ```js
132
- const api = new ApiFeatures(Model, req.query, 'user');
133
- api
134
- .addManualFilters({ status: 'active' }) // optional
135
- .filter()
136
- .sort()
137
- .limitFields()
138
- .paginate()
139
- .populate(['category','brand'])
140
- .execute();
141
- ```
85
+ ### 1. catchAsync(fn)
142
86
 
143
- ---
87
+ Wraps an async function to catch errors and pass them to Express error middleware.
144
88
 
145
- ## 📚 ApiFeatures Class Methods
89
+ * **Signature:** `catchAsync(fn: Function): Function`
146
90
 
147
- ### `.filter()`
91
+ * **Example:**
148
92
 
149
- * **Purpose**: Parse `req.query` and merge with optional manual filters, apply security filters (`forbiddenFields`, `isActive:true` for non-admin).
150
- * **Behavior**:
93
+ ```js
94
+ app.post(
95
+ '/api/v1/users',
96
+ catchAsync(async (req, res) => {
97
+ // async logic
98
+ })
99
+ );
100
+ ```
151
101
 
152
- 1. Remove pagination/sort/project/populate keys.
153
- 2. Parse comparison operators and logical (`$or/$and`).
154
- 3. Sanitize values (ObjectId, boolean, number).
155
- 4. Apply `$match` with forbidden fields stripped.
156
- * **Returns**: `this`.
102
+ ### 2. catchError(err, req, res, next)
157
103
 
158
- ### `.sort()`
104
+ Express error-handling middleware that returns standardized JSON errors.
159
105
 
160
- * **Purpose**: Generate `$sort` stage.
161
- * **Behavior**:
106
+ * **Response:**
162
107
 
163
- 1. Split comma-separated list.
164
- 2. Determine direction (`-` prefix = descending).
165
- 3. Validate against schema paths.
166
- 4. Push `{ $sort: {...} }` if valid.
167
- * **Returns**: `this`.
108
+ ```json
109
+ {
110
+ "status": "error" | "fail",
111
+ "message": "Error description",
112
+ "errors": [ /* optional array of details */ ]
113
+ }
114
+ ```
168
115
 
169
- ### `.limitFields()`
116
+ * **Usage:** Place after all routes
170
117
 
171
- * **Purpose**: Generate `$project` stage for field selection.
172
- * **Behavior**:
118
+ ### 3. HandleERROR
173
119
 
174
- 1. Split comma-separated fields.
175
- 2. Exclude `forbiddenFields`.
176
- 3. Validate against schema paths.
177
- 4. Push `{ $project: {...} }`.
178
- * **Returns**: `this`.
120
+ Custom `Error` subclass for operational errors.
179
121
 
180
- ### `.paginate()`
122
+ * **Constructor:** `new HandleERROR(message: string, statusCode: number)`
123
+ * **Example:**
181
124
 
182
- * **Purpose**: Add `$skip` and `$limit` for pagination.
183
- * **Behavior**:
125
+ ```js
126
+ if (!user) {
127
+ throw new HandleERROR('User not found', 404);
128
+ }
129
+ ```
184
130
 
185
- 1. Parse `page` and `limit`, default to 1 and 10.
186
- 2. Enforce `maxLimit` per role.
187
- 3. Push `{ $skip }` and `{ $limit }`.
188
- * **Returns**: `this`.
131
+ ---
189
132
 
190
- ### `.populate(input?)`
133
+ ## 🚀 ApiFeatures Class
191
134
 
192
- * **Purpose**: Add `$lookup`/`$unwind` stages for population.
193
- * **Input Types**: `string`, `{ path, select, populate? }`, `array`
194
- * **Behavior**:
135
+ Chainable class that translates HTTP query parameters into a secure MongoDB aggregation pipeline.
195
136
 
196
- 1. Collect inputs and `req.query.populate`.
197
- 2. Deduplicate and enforce `allowedPopulate` per role.
198
- 3. For each field:
137
+ ```js
138
+ const features = new ApiFeatures(
139
+ Model, // Mongoose model
140
+ req.query, // HTTP query object
141
+ req.user.role // User role for security (guest|user|admin|superAdmin)
142
+ )
143
+ .filter() // Filtering
144
+ .sort() // Sorting
145
+ .limitFields() // Field limiting
146
+ .paginate() // Pagination
147
+ .populate() // Population
148
+ .addManualFilters({ isActive: true }) // Manual filters
149
+ ;
150
+ const result = await features.execute({ allowDiskUse: true });
151
+ ```
199
152
 
200
- * Determine `collection` via `pluralize`.
201
- * Build `$lookup` with optional `pipeline` for projections.
202
- * Add `$unwind` preserving nulls.
203
- * **Returns**: `this`.
153
+ ### Constructor
204
154
 
205
- ### `.addManualFilters(filters)`
155
+ ```ts
156
+ new ApiFeatures(
157
+ model: mongoose.Model,
158
+ queryParams: Record<string, any> = {},
159
+ userRole: string = 'guest'
160
+ )
161
+ ```
206
162
 
207
- * **Purpose**: Inject custom filters before calling `.filter()`.
208
- * **Behavior**: Merge into internal `manualFilters`.
209
- * **Returns**: `this`.
163
+ * **model**: Mongoose model.
164
+ * **queryParams**: Typically `req.query`.
165
+ * **userRole**: Role key for security rules.
166
+
167
+ ### Chainable Methods
168
+
169
+ | Method | Description |
170
+ | ------------------------ | ---------------------------------------------------------------------------------------------------- |
171
+ | `.filter()` | Applies MongoDB operators. Supported operators: |
172
+ | | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `regex` (e.g. `?price[gt]=100&name[regex]=Book`). |
173
+ | `.sort()` | Sorting (e.g. `?sort=price,-name`). |
174
+ | `.limitFields()` | Field selection (e.g. `?fields=name,price`). |
175
+ | `.paginate()` | Pagination (e.g. `?page=2&limit=10`). |
176
+ | `.populate(paths?)` | Populate referenced documents. Accepts various input types (see below). |
177
+ | `.addManualFilters(obj)` | Add programmatic filters (e.g. `.addManualFilters({ isActive: true })`). |
178
+
179
+ > All methods return `this` for chaining.
180
+
181
+ #### Populate Input Formats
182
+
183
+ The `.populate()` method supports multiple ways to specify which paths to populate:
184
+
185
+ 1. **String (comma-separated)**
186
+
187
+ ```js
188
+ .populate('author,comments')
189
+ ```
190
+ 2. **Array of strings**
191
+
192
+ ```js
193
+ .populate(['author', 'comments'])
194
+ ```
195
+ 3. **Dot notation for nested paths**
196
+
197
+ ```js
198
+ // Populating nested field 'comments.author'
199
+ .populate('comments.author')
200
+ ```
201
+ 4. **Object with options**
202
+
203
+ ```js
204
+ .populate({
205
+ path: 'author', // field to populate
206
+ select: 'name email', // include only name and email
207
+ match: { isActive: true}, // only active authors
208
+ options: { limit: 5 } // limit populated docs
209
+ })
210
+ ```
211
+ 5. **Array of objects**
212
+
213
+ ```js
214
+ .populate([
215
+ { path: 'author', select: 'name' },
216
+ { path: 'comments', match: { flagged: false } }
217
+ ])
218
+ ```
210
219
 
211
- ### `.execute(options?)`
220
+ ---
212
221
 
213
- * **Purpose**: Execute the aggregation pipeline and return results.
214
- * **Options**:
222
+ ### execute(options)
215
223
 
216
- * `useCursor` (boolean): Return a cursor for streaming large sets.
217
- * `allowDiskUse` (boolean): Enable disk use.
218
- * `maxTimeMS` (number): Timeout for aggregation.
219
- * `debug` (boolean): Log the pipeline.
220
- * `projection` (object): Final projection on returned documents.
221
- * **Behavior**:
224
+ Executes aggregation pipeline.
225
+ (options)
222
226
 
223
- 1. Validate `maxPipelineStages`.
224
- 2. Optionally log pipeline.
225
- 3. Run `countPipeline` + `$count` to get total.
226
- 4. Run `pipeline` with or without cursor.
227
- 5. Apply `projection` to results if provided.
228
- * **Returns**: `{ success: true, count: number, data: array }`.
227
+ Executes aggregation pipeline.
229
228
 
230
- ---
229
+ * **Signature:**
231
230
 
232
- ## 🔄 Error Handling Utilities
231
+ ````ts
232
+ async execute(options?: {
233
+ useCursor?: boolean; // return cursor if true
234
+ allowDiskUse?: boolean; // enable disk use
235
+ projection?: Record<string, any>; // manual projection map
236
+ }): Promise<{
237
+ success: boolean;
238
+ count: number;
239
+ data: any[];
240
+ }>```
233
241
 
234
- ### `HandleERROR`
242
+ ````
243
+ * **Example Response:**
235
244
 
236
- Custom error class:
245
+ ```json
246
+ {
247
+ "success": true,
248
+ "count": 50,
249
+ "data": [ /* documents */ ]
250
+ }
251
+ ```
237
252
 
238
- ```js
239
- throw new HandleERROR('Not Found', 404);
240
- ```
253
+ ---
241
254
 
242
- ### `catchAsync(fn)`
255
+ ## 🔐 Security Configuration
243
256
 
244
- Wrap async handlers:
257
+ Customize rules in `security-config.js` at your project root (auto-generated):
245
258
 
246
259
  ```js
247
- app.get('/', catchAsync(async (req,res) => { /* ... */ }));
260
+ // security-config.js
261
+ export const securityConfig = {
262
+ allowedOperators: [
263
+ 'eq','ne','gt','gte','lt','lte','in','nin','regex'
264
+ ],
265
+ forbiddenFields: ['password','__v'],
266
+ accessLevels: {
267
+ guest: {
268
+ maxLimit: 20,
269
+ allowedPopulate: []
270
+ },
271
+ user: {
272
+ maxLimit: 100,
273
+ allowedPopulate: ['orders','profile']
274
+ },
275
+ admin: {
276
+ maxLimit: 1000,
277
+ allowedPopulate: ['*']
278
+ }
279
+ }
280
+ };
248
281
  ```
249
282
 
250
- ### `catchError`
283
+ * **allowedOperators**: Operators users can use in queries.
284
+ * **forbiddenFields**: Fields excluded from results.
285
+ * **accessLevels**: Per-role limits and populate permissions.
286
+
287
+ > If you omit `security-config.js`, defaults are applied from the package.
251
288
 
252
- Express error middleware:
289
+ ---
290
+
291
+ ## 🔍 Examples
292
+
293
+ ### Filter with Multiple Operators
294
+
295
+ ```http
296
+ GET /api/v1/products?price[gte]=50&price[lte]=200&category[in]=["books","electronics"]
297
+ ```
298
+
299
+ ### Manual Filter & Projection
253
300
 
254
301
  ```js
255
- app.use(catchError);
302
+ const features = new ApiFeatures(Product, req.query, 'user')
303
+ .addManualFilters({ isActive: true })
304
+ .filter()
305
+ .limitFields()
306
+ .execute({ projection: { name: 1, price: 1 } });
256
307
  ```
257
308
 
258
- ---
309
+ ### Populate Relations
259
310
 
260
- ## 🌟 Examples
311
+ ```http
312
+ GET /api/v1/posts?populate=author,comments
313
+ ```
261
314
 
262
- See the `examples/` directory for a full Express app demonstrating basic and advanced use cases.
315
+ ### Complete Controller Example
263
316
 
264
- ---
317
+ ```js
318
+ app.get(
319
+ '/api/v1/orders',
320
+ catchAsync(async (req, res) => {
321
+ const result = await new ApiFeatures(Order, req.query, req.user.role)
322
+ .filter()
323
+ .sort()
324
+ .limitFields()
325
+ .paginate()
326
+ .populate()
327
+ .execute();
265
328
 
266
- ## 🔬 Testing
329
+ res.json(result);
330
+ })
331
+ );
332
+ ```
267
333
 
268
- Unit tests powered by Jest:
334
+ ---
335
+
336
+ ## 🧪 Testing
269
337
 
270
338
  ```bash
271
339
  npm test
272
340
  ```
273
341
 
274
- ---
342
+ Tests use Jest. Add tests for your controllers and ApiFeatures behaviors.
275
343
 
276
- ## 🤝 Contributing
344
+ ---
277
345
 
278
- 1. Fork the repo
279
- 2. Create a feature branch: `git checkout -b feature/YourFeature`
280
- 3. Commit: `git commit -m 'Add awesome feature'`
281
- 4. Push: `git push origin feature/YourFeature`
282
- 5. Open a Pull Request
346
+ ## 📜 License
283
347
 
284
- Please follow existing code style, include tests, and update documentation if needed.
348
+ MIT © [Alireza Aghaee](https://github.com/alirezaaghaee)
285
349
 
286
350
  ---
287
351
 
288
352
  ## 📜 License
289
353
 
290
- Licensed under the MIT License. See [LICENSE](./LICENSE) for details.
354
+ MIT © 2024 Alireza Aghaee
355
+
356
+ ---
357
+
358
+ ## ✒️ Author
359
+
360
+ **Alireza Aghaee**
361
+
362
+ * GitHub: [AlirezaAghaee1996](https://github.com/AlirezaAghaee1996)
363
+ * LinkedIn: [alireza-aghaee-mern-dev](https://www.linkedin.com/in/alireza-aghaee-mern-dev)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanta-api",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Advanced API features and security configuration for Node.js/MongoDB.",
5
5
  "main": "index.js",
6
6
  "scripts": {