MongoDB
MongoDB is a first-class backend, not a translation layer bolted on: the same entity classes and the same JSON query run there, and UQL compiles them to find cursors or aggregation pipelines instead of SQL. What follows is only what differs from the SQL dialects.
Install and connect
Section titled “Install and connect”npm install uql-orm mongodbimport { setQuerierPool } from 'uql-orm';import { MongodbQuerierPool } from 'uql-orm/mongo';
export const pool = new MongodbQuerierPool(process.env.MONGO_URL!, { maxPoolSize: 10 });
setQuerierPool(pool);The second argument is the driver’s MongoClientOptions verbatim; the third is UQL’s extra options. pool.end() closes the client.
MongoDB stores the primary key as _id, and UQL maps it to whatever you named your @Id field on the way in and out, so entities stay portable:
import { Entity, Field, Id } from 'uql-orm';
@Entity()class User { @Id({ type: String }) id?: string; @Field({ type: String }) email?: string;}An id is an ObjectId in the database and a string in your code, converted in both directions. $exclude: { id: true } produces the _id: 0 projection MongoDB requires.
Relations
Section titled “Relations”To-one relations compile to $lookup stages in an aggregation pipeline. To-many relations run as a second query and are filled in afterwards, the same strategy the SQL dialects use, so $populate behaves identically.
A query that only reads scalar fields skips the pipeline and uses a plain find cursor, which is the faster path. Filtering on a relation forces aggregation, since a cursor cannot express the join.
Projection carries over to the aggregation path: $select and $exclude narrow the columns whether or not a relation is in play, and a relation’s own projection narrows it too. The $project stages are placed after the lookups have read the join keys, so nothing you populate is lost to a column you dropped.
Transactions need a replica set
Section titled “Transactions need a replica set”querier.transaction(...) opens a driver session and a real multi-document transaction, which MongoDB only supports on a replica set or a sharded cluster. Against a standalone mongod the server rejects it. A single-node replica set is enough in development:
docker run -p 27017:27017 mongo --replSet rs0 --bind_ip_all# then, once: mongosh --eval 'rs.initiate()'Isolation levels are a SQL concept, so isolationLevel is ignored here.
Streaming
Section titled “Streaming”findManyStream uses a native cursor, and it loads no relations at all: requesting any relation key throws rather than silently returning partial documents. Aggregation-based relation loading cannot be done row by row. See Streaming.
Search
Section titled “Search”- Full-text needs a
textindex, which declares its own fields.$texttherefore accepts$fieldsfor API consistency and ignores it. See Full-text search. - Vector search uses Atlas
$vectorSearch, so it needs a vector index defined in Atlas rather than in a migration. See Semantic search. - Hybrid search is native: MongoDB fuses a text match and a vector match with
$rankFusion, where Postgres needs a hand-written reciprocal-rank-fusion query.
Migrations
Section titled “Migrations”Migrations work, with the obvious caveat that a document store has no columns to alter. UQL creates collections and indexes, and does it outside any transaction, since MongoDB does not allow those operations inside one. Field-level changes are data migrations you write yourself.