Humongo

Mongo for humans

Humongo is a little language for writing MongoDB aggregation queries a little more tersely. Not every Mongo feature is supported, but keep in mind that it is possible to write raw queries and interpolate them with Humongo. Scroll down for docs.

Write your query here to compile to JS:

Mongo output

db.getCollection("orders").aggregate([ { $match: { purchase_date: { $gt: ISODate("2021-08-15"), $lt: ISODate("2021-08-23") }] }, } }, { $set: { line_item_ids: { $map: { input: "$line_items", in: "$$this.product_id" } }, } }, { $lookup: { from: "products", localField: "line_item_ids", foreignField: "_id", as: "products", } }, { $set: { label: { $cond: { if: { $lt: [{ $size: "$line_items" }, 6] }, then: "small", else: { $cond: { if: { $gte: [{ $size: "$line_items" }, 12] }, then: "large", else: "medium" } } } }, } }, { $project: { line_item_ids: true, label: true, tags: true, customer_id: true, } } ])

Docs

Aggregation Stages

$count

Default to labelling the field "count":

Humongo input

aggregate users: match name = "Fred" count

JS/Mongo query language output

db.getCollection("users").aggregate([ { $match: { name: { $eq: "Fred" }, } }, { $count: "count" } ])
Specify the field name:

Humongo input

aggregate users: count as user_count

JS/Mongo query language output

db.getCollection("users").aggregate([ { $count: "user_count" } ])

$group

Humongo input

aggregate users: group by signup_date: count = sum(1) list = push($$ROOT)

JS/Mongo query language output

db.getCollection("users").aggregate([ { $group: { _id: "$signup_date", count: { $sum: 1 }, list: { $push: "$$ROOT" }, } } ])

$limit

Humongo input

aggregate users: limit 1000

JS/Mongo query language output

db.getCollection("users").aggregate([ { $limit: 1000 } ])

$lookup

Called 'import' in Humongo:

Humongo input

aggregate users: import subs from subscriptions by customerId using _id

JS/Mongo query language output

db.getCollection("users").aggregate([ { $lookup: { from: "subscriptions", localField: "_id", foreignField: "customerId", as: "subs", } } ])
Omitting the collection name will cause collection name to be the same as the new field name. Omitting the localField will cause the localField to be the same as the collection name. Omitting foreignField defaults to '_id'.

Humongo input

aggregate users: import subscriptions

JS/Mongo query language output

db.getCollection("users").aggregate([ { $lookup: { from: "subscriptions", localField: "subscriptions", foreignField: "_id", as: "subscriptions", } } ])
To import a single element, use 'import one'.

Humongo input

aggregate users: import one order from orders using orderIds

JS/Mongo query language output

db.getCollection("users").aggregate([ { $lookup: { from: "orders", localField: "orderIds", foreignField: "_id", as: "order", } } , { $set: { order: { $arrayElemAt: ["$order", 0] } } } ])

$match

Tersely, on one line:

Humongo input

aggregate subscriptions: match name = "john"

JS/Mongo query language output

db.getCollection("subscriptions").aggregate([ { $match: { name: { $eq: "john" }, } } ])
Or spread across multiple lines:

Humongo input

aggregate subscriptions: match: name = "john" timestamp < "2021-08-23", > "2021-08-15"

JS/Mongo query language output

db.getCollection("subscriptions").aggregate([ { $match: { name: { $eq: "john" }, timestamp: { $and: [{ $lt: "2021-08-23" }, { $gt: "2021-08-15" }] }, } } ])

$project

Fields that should be directly projected can be listed on one line separated by commas.

Humongo input

aggregate users: project: age, birth_date name = last_name address = street_address_1

JS/Mongo query language output

db.getCollection("users").aggregate([ { $project: { age: true, birth_date: true, name: "$last_name", address: "$street_address_1", } } ])

$set

Tersely, on one line:

Humongo input

aggregate users: set name = "john"

JS/Mongo query language output

db.getCollection("users").aggregate([ { $set: { name: "john", } } ])
Or spread across multiple lines:

Humongo input

aggregate users: set: name = "john" age = 47

JS/Mongo query language output

db.getCollection("users").aggregate([ { $set: { name: "john", age: 47, } } ])

$sortByCount

Humongo input

aggregate users: count by size(orders)

JS/Mongo query language output

db.getCollection("users").aggregate([ { $sortByCount: { $size: "$orders" } } ])

$unwind

Humongo input

aggregate users: unwind orders

JS/Mongo query language output

db.getCollection("users").aggregate([ { $unwind: "$orders" } ])

Interpolate raw JavaScript

Use single-quotes to interpolate raw JavaScript (or anything else).

Humongo input

aggregate users: match name = "fred" '{ $set: { tags: { $concat: ["$tags", ", checked"] } } }' unwind orders

JS/Mongo query language output

db.getCollection("users").aggregate([ { $match: { name: { $eq: "fred" }, } }, { $set: { tags: { $concat: ["$tags", ", checked"] } } }, { $unwind: "$orders" } ])

Expressions

Array Element Access

Use C-style array element access.

Humongo input

aggregate users: set first_order = orders[0]

JS/Mongo query language output

db.getCollection("users").aggregate([ { $set: { first_order: { $arrayElemAt: ["$orders", 0] }, } } ])

Array and object literals

Array and object literals work like you expect.

Humongo input

aggregate items: set list = [ 1, 2, "thing", { stuff: "junk" } ]

JS/Mongo query language output

db.getCollection("items").aggregate([ { $set: { list: [1, 2, "thing", { stuff: "junk" }], } } ])

Humongo input

aggregate users: set order = { dog: "fido", cat: "snuffles" }

JS/Mongo query language output

db.getCollection("users").aggregate([ { $set: { order: { dog: "fido", cat: "snuffles" }, } } ])

Complex Operators

For MongoDB operators that require a spec object, simply pass the object using function notation.

Humongo input

aggregate users: project: orders = map({ input: orders, in: $$this.id })

JS/Mongo query language output

db.getCollection("users").aggregate([ { $project: { orders: { $map: { input: "$orders", in: "$$this.id" } }, } } ])

Conditionals

Use C-style ternary syntax for conditional expressions.

Humongo input

aggregate things: set tag = size(things) > 5 ? "big" : "small"

JS/Mongo query language output

db.getCollection("things").aggregate([ { $set: { tag: { $cond: { if: { $gt: [{ $size: "$things" }, 5] }, then: "big", else: "small" } }, } } ])

Dates

Date literals consist of @@ followed by a date in ISO format.

Humongo input

aggregate orders: match purchase_date <= @@2021-08-15, > @@2021-08-07

JS/Mongo query language output

db.getCollection("orders").aggregate([ { $match: { purchase_date: { $and: [{ $lte: ISODate("2021-08-15") }, { $gt: ISODate("2021-08-07") }] }, } } ])

Functions

Most operators are verbatim from MongoDB, using standard function invocation notation. Note that 'all' and 'any' alias 'allElementsTrue' and 'anyElementTrue'. And some operators that require an extra array wrapping do not require it in Humongo.

Humongo input

aggregate things: set: xyz = cos(abc) abc = split(name, ",") def = all(one, two, three, four)

JS/Mongo query language output

db.getCollection("things").aggregate([ { $set: { xyz: { $cos: "$abc" }, abc: { $split: ["$name", ","] }, def: { $allElementsTrue: ["$one", "$two", "$three", "$four"] }, } } ])

Interpolating JavaScript

Use single quotes to interpolate JavaScript in the place of any valid expression.

Humongo input

aggregate users: match date < 'moment().subtract(5, "days").toDate()'

JS/Mongo query language output

db.getCollection("users").aggregate([ { $match: { date: { $lt: moment().subtract(5, "days").toDate() }, } } ])

ObjectIDs

ObjectID literals begin with '#' followed by 12 hexadecimal digits

Humongo input

aggregate users: match _id = #111111111111

JS/Mongo query language output

db.getCollection("users").aggregate([ { $match: { _id: { $eq: ObjectId("111111111111") }, } } ])

Operators

Most operators (+, -, <) work like you'd expect. Note that '@' aliases Mongo's '$in' (both in query operations and expressions)

Humongo input

aggregate users: match name @ ["john", "fred", "george"]

JS/Mongo query language output

db.getCollection("users").aggregate([ { $match: { name: { $in: ["john", "fred", "george"] }, } } ])

Type conversion

Use `as` to convert to different types (as well as Upper and Lower case).

Humongo input

aggregate users: set price = cost as Decimal

JS/Mongo query language output

db.getCollection("users").aggregate([ { $set: { price: { $toDecimal: "$cost" }, } } ])