Skip to content

feat: add advanced query operators and robust error handling#115

Merged
yash-pouranik merged 3 commits intogeturbackend:mainfrom
MdTowfikomer:feature/advanced-query-operators
Apr 18, 2026
Merged

feat: add advanced query operators and robust error handling#115
yash-pouranik merged 3 commits intogeturbackend:mainfrom
MdTowfikomer:feature/advanced-query-operators

Conversation

@MdTowfikomer
Copy link
Copy Markdown
Contributor

@MdTowfikomer MdTowfikomer commented Apr 18, 2026

🚀 Pull Request Description

Fixes #99

🛠️ Type of Change

  • ✨ New feature: Added advanced query operators: _ne, _in, _exists, _regex
  • 📝 Documentation update: Updated Mintlify and legacy API docs
  • ⚙️ Refactor / Chore: Modularized QueryEngine logic into _buildMongoQuery
  • Also updated controller error handling so invalid query filters (like bad regex)
    return a client-safe 400 response.

🧪 Testing & Validation

Backend Verification:

  • Created a new, dedicated unit test suite for the QueryEngine:
    • packages/common/src/utils/__tests__/queryEngine.test.js.
  • All 9 tests passed, verifying:
    • Basic equality and existing comparisons (_gt, _lt).
    • The 4 new advanced operators.
  • Added controller-level test for invalid regex returning 400 in:
    • apps/public-api/src/__tests__/data.controller.read.test.js
    • Defensive Edge Cases: Verified that repeated query params for _in (arrays) and invalid regex
      patterns for _regex do not crash the server (preventing 500 errors).
  • Ran linting across the workspace to ensure code quality.

📝 Description of Changes
Expanded filtering support in packages/common/src/utils/queryEngine.js for GET /api/data/:collectionName:

Key Technical Enhancements:

  1. _ne (Not Equal): Added support for excluding specific values.
  2. _in (In Set): Implemented logic to handle comma-separated values. Added defensive mapping to
    support cases where Express parses repeated parameters as an array, preventing .split()
    crashes.
  3. _exists (Existence): Added field presence checks with automatic string-to-boolean conversion
    ("true"/"false").
  4. _regex (Pattern Match): Implemented case-insensitive search. Added a try-catch block to
    gracefully handle invalid regex syntax from user input, ensuring the server remains stable.
  5. Refactor: Extracted the query building logic into _buildMongoQuery for better modularity and
    future reuse.
  6. Docs: Updated mintlify/docs/guides/database.mdx and docs-legacy/api-reference.md with clear
    tables and examples for the new operators.

✅ Checklist

  • My code follows the project style.
  • I have self-reviewed my changes.
  • I updated relevant documentation.
  • My changes do not introduce new test failures.

Built with ❤️ for urBackend.

Summary by CodeRabbit

  • Bug Fixes

    • Improved error handling to return HTTP 400 for client/query validation errors (including invalid filter patterns) and HTTP 500 for server failures.
  • New Features

    • Added field-suffix filters: _gte, _lte, _ne, _in, _exists, and _regex for richer querying.
    • Regex filters are case-insensitive, have a 128-character cap, and invalid/oversized patterns produce HTTP 400.
  • Documentation

    • Updated API reference and guides to show the new suffix-style filtering and regex constraints.
  • Tests

    • Added tests covering new filter behaviors and error handling.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

📝 Walkthrough

Walkthrough

Extended filtering with new suffix operators (_ne, _in, _exists, _regex), added QueryEngine.QueryFilterError (400), applied regex timeouts to queries, updated controller to map query-validation errors to HTTP 400, added tests, and updated documentation to reflect new filter syntax and constraints.

Changes

Cohort / File(s) Summary
QueryEngine Core
packages/common/src/utils/queryEngine.js
Added QueryFilterError (name + statusCode=400), new static limits (MAX_REGEX_PATTERN_LENGTH, REGEX_MAX_TIME_MS), support for _ne, _in, _exists, _regex, validation for regex length/format, and tracks hasRegexFilter to apply maxTimeMS when supported.
QueryEngine Tests
packages/common/src/utils/__tests__/queryEngine.test.js
New tests covering equality, comparison ops, _ne, _in (csv and arrays), _exists boolean parsing, _regex creation and maxTimeMS use, and error cases for invalid/oversized regex.
API Controller & Tests
apps/public-api/src/controllers/data.controller.js, apps/public-api/src/__tests__/data.controller.read.test.js
getAllData: apply maxTimeMS to count when regex present, capture countQuery before await; catch block now returns 400 for statusCode===400 or name==='QueryFilterError' with { success:false, data:{}, message }, else 500 with generic failure message. Added test asserting 400 response for invalid regex.
Documentation
docs-legacy/api-reference.md, mintlify/docs/guides/database.mdx
Documented new field-suffix filter syntax (e.g., age_gt, price_in), mapping to Mongo operators, added _regex pattern length cap (128) and 400 behavior for invalid/oversized patterns; updated examples.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as API Controller\n(getAllData)
    participant QE as QueryEngine
    participant DB as MongoDB

    Client->>Controller: GET /data?name_regex=[
    Controller->>QE: instantiate + engine.filter()
    QE->>QE: parse filters\nvalidate _regex (length/format)
    alt regex invalid
        QE-->>Controller: throw QueryFilterError\n(statusCode=400)
        Controller->>Controller: catch -> detect 400/error name
        Controller-->>Client: HTTP 400 { success:false, data:{}, message }
    else regex valid
        QE->>DB: query.find(...){ maxTimeMS? }
        DB-->>QE: results
        QE-->>Controller: filtered results
        Controller->>DB: if count path -> countQuery (apply maxTimeMS if regex)
        DB-->>Controller: count
        Controller-->>Client: HTTP 200 { success:true, data, meta }
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Poem

🐰 I nibble patterns, sort the fields with glee,
_in and _regex now hop beside me,
Bad patterns get a gentle 400 thump,
Queries run tidy — no more regex clump! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add advanced query operators and robust error handling' directly matches the PR's main changes: new query operators (_ne, _in, _exists, _regex) and improved error handling for 400 vs 500 responses.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
packages/common/src/utils/__tests__/queryEngine.test.js (1)

103-108: Consider asserting the error type, not just the message.

The controller branches on err.name === 'QueryFilterError' / err.statusCode === 400, but the test only checks the message substring. Adding an assertion against QueryEngine.QueryFilterError / statusCode would lock in the contract the controller actually depends on.

-        expect(() => engine.filter()).toThrow('Invalid regex pattern');
+        expect(() => engine.filter()).toThrow(QueryEngine.QueryFilterError);
+        try { engine.filter(); } catch (e) { expect(e.statusCode).toBe(400); }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/src/utils/__tests__/queryEngine.test.js` around lines 103 -
108, The test currently only asserts the error message for invalid regex in the
QueryEngine.filter call; update the test to also assert the error type and/or
status code the controller relies on by expecting the thrown error to be an
instance of QueryEngine.QueryFilterError and to have statusCode === 400 (or
assert err.name === 'QueryFilterError' and err.statusCode === 400) when calling
new QueryEngine(mockQuery, queryString).filter(); this ensures the test locks in
the contract used by the controller.
apps/public-api/src/__tests__/data.controller.read.test.js (1)

148-169: Good coverage — consider also covering name === 'QueryFilterError'.

The test exercises the statusCode === 400 branch. Since the controller also accepts err.name === 'QueryFilterError' as an OR, a second case (error without statusCode but with name = 'QueryFilterError') would protect that branch from regression.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/public-api/src/__tests__/data.controller.read.test.js` around lines 148
- 169, Add a new test variant to exercise the branch where the controller treats
errors with err.name === 'QueryFilterError' as a 400: in the same test suite
create a test similar to "getAllData returns 400 when QueryEngine throws query
validation error" that uses mockQueryEngine.mockImplementationOnce to return an
object whose filter() throws an Error instance with name set to
'QueryFilterError' (and without statusCode), call getAllData(req, res) with the
same invalid query (e.g., name_regex: '['), then assert res.status was called
with 400 and res.json was called with the expected error message; reference the
existing test name, mockQueryEngine, and getAllData to place the new case.
packages/common/src/utils/queryEngine.js (1)

45-52: _in values are always strings.

String(rawValue).split(',') forces all elements to strings, so numeric/ObjectId/boolean fields require Mongoose schema casting to match. In the getAllData path, Model.find() does cast based on schema so this typically works, but schemaless/loose collections or fields not typed in the compiled model will silently return no matches. Worth a brief doc note or a cast hint; not blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/src/utils/queryEngine.js` around lines 45 - 52, The `_in`
branch always coerces values to strings via String(rawValue).split(',') which
prevents numeric/boolean/ObjectId values from matching schemaless or
loosely-typed collections; update the logic in the key.endsWith('_in') block
(operating on queryObj and writing to mongoQuery) to attempt type-preserving
parsing for each token before adding the $in array—e.g. if a token looks like a
number parseNumber, if "true"/"false" parseBoolean, if a 24-hex pattern treat as
an ObjectId candidate, or generally try JSON.parse and fall back to the original
string—so values retain their native types when passed to Model.find() in
getAllData. Ensure you still support both comma-separated strings and
repeated-array params.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/public-api/src/controllers/data.controller.js`:
- Around line 164-170: The catch block currently returns raw { error:
err.message } and may leak Mongo internals; update it to return the project's
response envelope and avoid exposing internal messages: for known client errors
(err.statusCode === 400 or err.name === 'QueryFilterError') respond with
res.status(400).json({ success: false, data: null, message: err.message }) and
for all other errors log the full err server-side then respond with
res.status(500).json({ success: false, data: null, message: 'Internal server
error' }); also ensure any thrown/propagated errors use the AppError class
instead of raw throws so other callers expect AppError semantics (refer to this
controller's catch block and the aggregateData handler for examples).

In `@mintlify/docs/guides/database.mdx`:
- Line 103: Update the documentation heading "Advanced Filtering" to sentence
case by changing the heading text to "Advanced filtering" in the
mintlify/docs/guides/database.mdx file; locate the heading string "Advanced
Filtering" and replace it with "Advanced filtering" so it follows the project's
sentence-case style guide for headings.

In `@packages/common/src/utils/queryEngine.js`:
- Around line 53-56: The _exists handler in queryEngine.js silently treats any
non-'true' string as false; update the block that handles keys ending with
'_exists' (the code using key.replace(/_exists$/, ''), value = queryObj[key] ===
'true' and assigning mongoQuery[field].$exists) to explicitly accept only the
literal strings 'true' or 'false', convert them to booleans when valid, and
throw a QueryFilterError for any other value (mirroring the _regex validation
behavior) so invalid inputs like "1" or "yes" produce a clear error instead of
silently flipping semantics.

---

Nitpick comments:
In `@apps/public-api/src/__tests__/data.controller.read.test.js`:
- Around line 148-169: Add a new test variant to exercise the branch where the
controller treats errors with err.name === 'QueryFilterError' as a 400: in the
same test suite create a test similar to "getAllData returns 400 when
QueryEngine throws query validation error" that uses
mockQueryEngine.mockImplementationOnce to return an object whose filter() throws
an Error instance with name set to 'QueryFilterError' (and without statusCode),
call getAllData(req, res) with the same invalid query (e.g., name_regex: '['),
then assert res.status was called with 400 and res.json was called with the
expected error message; reference the existing test name, mockQueryEngine, and
getAllData to place the new case.

In `@packages/common/src/utils/__tests__/queryEngine.test.js`:
- Around line 103-108: The test currently only asserts the error message for
invalid regex in the QueryEngine.filter call; update the test to also assert the
error type and/or status code the controller relies on by expecting the thrown
error to be an instance of QueryEngine.QueryFilterError and to have statusCode
=== 400 (or assert err.name === 'QueryFilterError' and err.statusCode === 400)
when calling new QueryEngine(mockQuery, queryString).filter(); this ensures the
test locks in the contract used by the controller.

In `@packages/common/src/utils/queryEngine.js`:
- Around line 45-52: The `_in` branch always coerces values to strings via
String(rawValue).split(',') which prevents numeric/boolean/ObjectId values from
matching schemaless or loosely-typed collections; update the logic in the
key.endsWith('_in') block (operating on queryObj and writing to mongoQuery) to
attempt type-preserving parsing for each token before adding the $in array—e.g.
if a token looks like a number parseNumber, if "true"/"false" parseBoolean, if a
24-hex pattern treat as an ObjectId candidate, or generally try JSON.parse and
fall back to the original string—so values retain their native types when passed
to Model.find() in getAllData. Ensure you still support both comma-separated
strings and repeated-array params.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0886c2f7-c9a8-4605-bc72-675d9895288d

📥 Commits

Reviewing files that changed from the base of the PR and between 6374cc1 and 3e73995.

📒 Files selected for processing (6)
  • apps/public-api/src/__tests__/data.controller.read.test.js
  • apps/public-api/src/controllers/data.controller.js
  • docs-legacy/api-reference.md
  • mintlify/docs/guides/database.mdx
  • packages/common/src/utils/__tests__/queryEngine.test.js
  • packages/common/src/utils/queryEngine.js

Comment thread apps/public-api/src/controllers/data.controller.js
Comment thread mintlify/docs/guides/database.mdx Outdated
Comment thread packages/common/src/utils/queryEngine.js Outdated
Comment thread packages/common/src/utils/queryEngine.js
@MdTowfikomer
Copy link
Copy Markdown
Contributor Author

@yash-pouranik, These are great catches, especially on the defensive logic for _in and _regex. I’m working on implementing the error handling refinements, type-aware parsing, and the requested test/docs updates now. I'll push the changes shortly

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mintlify/docs/guides/database.mdx`:
- Around line 117-124: Update the docs to make the examples copy-paste safe by
URL-encoding regex metacharacters in the `_regex`/`$regex` examples (e.g.,
encode ^, $, etc.) and update the filtering description to state that `price_gt`
and `price_lt` are exclusive bounds (strictly greater than / strictly less than)
rather than “between”; adjust the example curl query for the posts endpoint to
use an encoded regex and keep the `status=published`, `price_gt`, `price_lt`,
and `sort=-createdAt` parameters as-is.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f07af80c-3a80-4825-ae08-de77ad53be1e

📥 Commits

Reviewing files that changed from the base of the PR and between 3e73995 and 11dcac5.

📒 Files selected for processing (6)
  • apps/public-api/src/__tests__/data.controller.read.test.js
  • apps/public-api/src/controllers/data.controller.js
  • docs-legacy/api-reference.md
  • mintlify/docs/guides/database.mdx
  • packages/common/src/utils/__tests__/queryEngine.test.js
  • packages/common/src/utils/queryEngine.js
✅ Files skipped from review due to trivial changes (1)
  • docs-legacy/api-reference.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/public-api/src/tests/data.controller.read.test.js
  • packages/common/src/utils/tests/queryEngine.test.js
  • packages/common/src/utils/queryEngine.js

Comment thread mintlify/docs/guides/database.mdx Outdated
@mintlify
Copy link
Copy Markdown
Contributor

mintlify Bot commented Apr 18, 2026

Updated guides/database.mdx to URL-encode the regex metacharacter ^ as %5E in the _regex example and clarified that price_gt/price_lt are exclusive (strictly greater/less than) bounds in the filtering description.

@MdTowfikomer
Copy link
Copy Markdown
Contributor Author

@yash-pouranik, could you check & verify the changes? Also, since I'm a first-time contributor, could you please guide me if I'm making any mistakes?

@yash-pouranik
Copy link
Copy Markdown
Collaborator

Yes just reviewing

@yash-pouranik yash-pouranik self-requested a review April 18, 2026 18:08
Copy link
Copy Markdown
Collaborator

@yash-pouranik yash-pouranik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR @MdTowfikomer

@yash-pouranik
Copy link
Copy Markdown
Collaborator

What score to give this PR??
level 1 - 3pt
level 2 - 5pt
level 3 - 10pt

I suggest lvl 2 as how nicely he cooprrated with ur reviews and did changes in multiple files and synced all the changes in doc as well

What say @coderabbitai

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

Tip

For best results, initiate chat on the files or code changes.

@yash-pouranik, happy to share my perspective based on the technical scope of this PR!

Here's a quick breakdown of what was delivered:

Area What was done
Core logic 4 new query operators (_ne, _in, _exists, _regex) + _buildMongoQuery refactor
Error handling Custom QueryFilterError class, statusCode-aware catch blocks, safe 400 vs 500 responses
Safety guardrails Regex length cap, maxTimeMS timeout, defensive _in array handling
Tests 9 unit tests + controller-level test for invalid regex → 400
Docs Updated both mintlify/docs/guides/database.mdx and docs-legacy/api-reference.md

My take: Level 2 (5pt) is the right call, and I'd agree with your suggestion. Here's my reasoning:

  • ✅ Changes span multiple packages/apps and are well-coordinated
  • ✅ The contributor iterated on feedback, fixed defensive edge cases, and kept docs in sync — that's good collaborative behavior
  • ✅ The work is non-trivial (custom error class, static constants, operator parsing logic)
  • The PR stops short of Level 3 because it's an incremental feature addition rather than a foundational architectural change (e.g., new service, new major subsystem, or a large-scale refactor)

So Level 2 / 5pt seems like a fair and well-reasoned reward. 👍

@yash-pouranik yash-pouranik added enhancement New feature or request NSOC'26 level-2 5 - points labels Apr 18, 2026
@yash-pouranik
Copy link
Copy Markdown
Collaborator

Once again thanks @MdTowfikomer
Hope to see more contribution from you, also dont forget to star the REPO
also go here - https://urbackend.bitbros.in/ scroll down and enter email to join pro after a month I think I am working on that.

@yash-pouranik yash-pouranik merged commit 0f96f3e into geturbackend:main Apr 18, 2026
7 checks passed
@MdTowfikomer
Copy link
Copy Markdown
Contributor Author

Thanks @yash-pouranik, I really appreciate it!
I’ll keep contributing and working on more issues going forward.

I’ve starred the repo and will check out the site as well.

@MdTowfikomer MdTowfikomer deleted the feature/advanced-query-operators branch April 18, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request level-2 5 - points NSOC'26

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Backend] Advanced Query Operators for Data API **Difficulty**: Intermediate | **Area**: packages/common

2 participants