Filter Compiled in the browser, right now
$.items.sumBy((i) => i.qty * i.price) > 100
MongoDB MQL
compiling…
JSMQL is plain JavaScript syntax that compiles to MongoDB's query language. You give it everyday JavaScript, and it gives you native MQL.
$.items.sumBy((i) => i.qty * i.price) > 100
compiling…
[$.building && $.building + ",", $.streetNo, $.street, $.suburb, $.state, $.country, $.postcode]
.filter(Boolean)
.join(" ")
compiling…
'Order #' || n || ': ' || total || ' AUD'
CONCAT('Order #', n, ': ', total, ' AUD')
'Order #' + CAST(n AS varchar) + ': '
+ CAST(total AS varchar) + ' AUD'
`Order #${$.n}: ${$.total} AUD`
MQL in the playground →
DATE_TRUNC('month', created_at)
DATE_FORMAT(created_at, '%Y-%m-01')
DATETRUNC(month, created_at)
$.createdAt.startOf("month")
MQL in the playground →
COALESCE((SELECT string_agg(t.tag, ', ')
FROM order_tags t WHERE t.order_id = o.id), '')
$.tags.join(", ")
MQL in the playground →
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 →
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 →
(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 →
Every MQL document on this page is compiled in the browser.
$match($.age >= 18 && $.region === "AU");
$group({ _id: $.shopId, total: $sum($.amount) });
$sort({ total: -1 });
compiling…
$.items.map("price").sum()
compiling…
+= and delete$.score += 1;
delete $.tempToken;
$.status = "done";
compiling…
$lookup$.customer = $$$.customers.find({ _id: $.customerId });
$.payments = $$$.payments.filter({ orderId: $._id, status: "paid" });
compiling…
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.
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.
Any MQL that you paste into JSMQL comes out unchanged. Existing MQL code works immediately.
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.
Your IDE syntax highlighting, formatting and refactoring will work without a plugin. Autocomplete for every operator ships in the package.
JSMQL makes sure that queries use collection indexes as much as possible.
Every error message tries to suggest a fix for the syntax error it reports.
JSMQL makes sure it never generates invalid MQL. An error can happen only if you write
$typo yourself.
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.
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.
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.
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.
$ is the document, $$ the collection being queried, $$$ the
database, $$$$ the current MongoDB server.
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.
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")).
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.
$ npm install @koresar/jsmql