JSMQL

Complex MongoDB queries in simple JavaScript

JSMQL is plain JavaScript syntax that compiles to MongoDB's query language. You give it everyday JavaScript, and it gives you native MQL.

Try the playground $ npm install @koresar/jsmql

Filter Compiled in the browser, right now

JSMQL
$.items.sumBy((i) => i.qty * i.price) > 100
MongoDB MQL
compiling…

One expression. Seventy lines of MQL.

Expression Full address from six optional fields

JSMQL
[$.building && $.building + ",", $.streetNo, $.street, $.suburb, $.state, $.country, $.postcode]
  .filter(Boolean)
  .join(" ")
MongoDB MQL
compiling…

SQL vs JSMQL

SQLJSMQL
Build a string
PostgreSQL
'Order #' || n || ': ' || total || ' AUD'
MySQL
CONCAT('Order #', n, ': ', total, ' AUD')
SQL Server
'Order #' + CAST(n AS varchar) + ': '
          + CAST(total AS varchar) + ' AUD'
`Order #${$.n}: ${$.total} AUD`
MQL in the playground →
Start of the month
PostgreSQL
DATE_TRUNC('month', created_at)
MySQL
DATE_FORMAT(created_at, '%Y-%m-01')
SQL Server
DATETRUNC(month, created_at)
$.createdAt.startOf("month")
MQL in the playground →
Join the tags into one string
COALESCE((SELECT string_agg(t.tag, ', ')
   FROM order_tags t WHERE t.order_id = o.id), '')
$.tags.join(", ")
MQL in the playground →
Filter after the group
SELECT customer_id, SUM(total) AS spent FROM orders
GROUP BY customer_id HAVING SUM(total) > 1000
$$.$group({ _id: $.customerId, spent: $sum($.total) })
  .filter((c) => c.spent > 1000);
MQL in the playground →
Latest order per customer
SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER
    (PARTITION BY customer_id ORDER BY created_at DESC) AS rn
  FROM orders) t WHERE rn = 1
$$.orderBy({ createdAt: -1 }).uniqBy("customerId");
MQL in the playground →
Count related rows
(SELECT COUNT(*) FROM payments p
   WHERE p.order_id = o.id AND p.status = 'failed')
$.failed = $$$.payments.filter(
  (p) => p.orderId === $._id && p.status === "failed",
).length;
MQL in the playground →

How it compiles

Every MQL document on this page is compiled in the browser.

Pipeline Stages are statements

JSMQL
$match($.age >= 18 && $.region === "AU");
$group({ _id: $.shopId, total: $sum($.amount) });
$sort({ total: -1 });
MongoDB MQL
compiling…

Expression Array work reads like JavaScript

JSMQL
$.items.map("price").sum()
MongoDB MQL
compiling…

Update Assignment, += and delete

JSMQL
$.score += 1;
delete $.tempToken;
$.status = "done";
MongoDB MQL
compiling…

Pipeline Joins without hand-writing $lookup

JSMQL
$.customer = $$$.customers.find({ _id: $.customerId });
$.payments = $$$.payments.filter({ orderId: $._id, status: "paid" });
MongoDB MQL
compiling…

Why JSMQL

MQL is an AST

A developer thinks $.qty * $.price + $.shipping. MongoDB needs { $add: [{ $multiply: ["$qty", "$price"] }, "$shipping"] }. That is the abstract syntax tree of the expression. When you write MQL by hand, you write an AST by hand.

LLM-generated MQL still needs to be reviewed

A person must still review code that an LLM generates. A smaller diff gives a faster review. A person reads five lines of JavaScript in seconds, but not 250 lines of nested $reduce. A smaller diff also helps the LLM, because it generates less code and makes fewer mistakes.

Existing MQL keeps working

Any MQL that you paste into JSMQL comes out unchanged. Existing MQL code works immediately.

Every operator is a function call

You can write any MongoDB operator as a function. $op(args) becomes { $op: args }. $dateTrunc({ date: $.createdAt, unit: "week" }) compiles to { $dateTrunc: { date: "$createdAt", unit: "week" } }. $setUnion($.tags, $.extra) compiles to { $setUnion: ["$tags", "$extra"] }. This also works for operators that MongoDB does not have yet.

Plain JavaScript syntax

Your IDE syntax highlighting, formatting and refactoring will work without a plugin. Autocomplete for every operator ships in the package.

Indexes keep working

JSMQL makes sure that queries use collection indexes as much as possible.

Errors suggest a fix

Every error message tries to suggest a fix for the syntax error it reports.

Never invalid MQL

JSMQL makes sure it never generates invalid MQL. An error can happen only if you write $typo yourself.

Not server-side JavaScript

MongoDB 8.0 deprecated $function, $accumulator and $where. These ran JavaScript inside the server, for each document, without indexes. This was the right choice. JSMQL compiles ahead of time, on the application side. The server only ever sees ordinary MQL.

Computations on the server

A typical pattern is this: you fetch all the documents, then you filter, group and sum them on the client side. JSMQL helps you move that computation to the database server.

No need to learn a new programming language

Two in three developers write JavaScript. People download lodash tens of millions of times a week. You already know words such as .filter, .groupBy and .sumBy.

SQL over MongoDB keeps failing

MongoDB offered analysts SQL three times since 2015: a BI connector built on PostgreSQL, then its Atlas successor, now deprecated, and a read-only SQL-92 interface. Each attempt assumed that people who need data speak SQL. JSMQL assumes they speak JavaScript.

Simple way to reference sibling collections

$ is the document, $$ the collection being queried, $$$ the database, $$$$ the current MongoDB server.

Parse once, run many times

On a hot path, do not compile the same JSMQL on every request. jsmql.compile((params, { $ }) => …) parses it once, at startup. It returns a function. Each call only binds the parameter values.

Mongoose plugin

Model methods accept JSMQL wherever they take a filter, an update or a pipeline. Plain MQL still works, unchanged. For example: UserModel.find(`$.email.endsWith("@gmail.com")`), or OrderModel.aggregate(() => $$.groupBy("sku")).

Command line

echo '$.age > 18' | jsmql prints { age: { $gt: 18 } }. The command line compiles JSMQL to MQL in a shell. This is useful for LLM coding tools.

Get started

Install

$ npm install @koresar/jsmql

  • Playground — JSMQL is on the left, and its MQL is on the right.

Read

  • Language reference — this describes the syntax, what each piece compiles to, and where it departs from JavaScript.
  • Worked examples — complete queries, each checked against the exact MQL it produces.
  • Language rules — the promises the compiler keeps, including that the server never gets MQL that cannot run.