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: everyday JavaScript in, native MQL out.
$.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. Writing MQL by hand is writing an AST by hand.
Code generated by an LLM still has to be reviewed by a person. The smaller the diff, the faster the
review: five lines of JavaScript are read in seconds; 250 lines of nested $reduce are not.
LLMs like smaller diffs too — less to generate, less to get wrong.
Any MQL pasted into JSMQL comes out unchanged, so existing MQL code will work out of the box.
Any MongoDB operator can be written as a function: $op(args) becomes
{ $op: args }. $dateTrunc({ date: $.createdAt, unit: "week" }) compiles to
{ $dateTrunc: { date: "$createdAt", unit: "week" } }, and
$setUnion($.tags, $.extra) to { $setUnion: ["$tags", "$extra"] }. This also
covers operators that do not exist 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 collection indexes are used 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 — unless you write $typo yourself.
MongoDB 8.0 deprecated $function, $accumulator and $where:
JavaScript running inside the server, per document, without indexes. Rightly. JSMQL compiles ahead of
time, on the application side; the server only ever sees ordinary MQL.
A typical pattern: fetch all the documents, then 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, and lodash is downloaded tens of millions of times a week.
.filter, .groupBy, .sumBy are already known words.
Since 2015 MongoDB has offered analysts SQL three times: a BI connector built on PostgreSQL, then its Atlas successor now deprecated, and a read-only SQL-92 interface. Each assumed the 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 the same JSMQL should not be compiled on every request.
jsmql.compile((params, { $ }) => …) parses it once, at startup, and 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 keeps working
untouched. UserModel.find(`$.email.endsWith("@gmail.com")`), or
OrderModel.aggregate(() => $$.groupBy("sku")).
echo '$.age > 18' | jsmql prints { age: { $gt: 18 } }. Compiles JSMQL to MQL
in a shell — handy for LLM coding tools.