The first database driver that executes native SQL against MongoDB. Not a translator — a full execution engine. JOINs, subqueries, window functions, transactions. SQL in, results out.
SELECT u.name, COUNT(o.id) AS orders FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE u.status = 'active' GROUP BY u.name HAVING COUNT(o.id) > 5 ORDER BY orders DESC LIMIT 10
[
{ $lookup: {
from: "orders",
localField: "id",
foreignField: "user_id",
as: "o" }},
{ $unwind: "$o" },
{ $match: { status: "active" }},
{ $group: {
_id: "$name",
orders: { $sum: 1 }}},
{ $match: { orders: { $gt: 5 }}},
{ $sort: { orders: -1 }},
{ $limit: 10 }
]
npm install strictdb
Architecture
Every SQL query passes through a three-phase pipeline that parses, resolves dependencies, and builds native MongoDB operations.
SQL string is parsed into an AST. Tables, columns, joins, subqueries, CTEs, and expressions are extracted and validated.
Subqueries, CTEs, and correlated expressions are resolved in dependency order. Independent branches run in parallel.
The resolved AST is compiled into MongoDB aggregate pipelines, bulkWrite operations, or multi-stage transactions and executed natively.
SQL String | v [ Parse ] -- node-sql-parser --> AST | v [ Resolve ] -- subqueries, CTEs --> resolved values | v [ Build ] -- AST + values --> $aggregate / $bulkWrite | v MongoDB executes natively | v Results returned to you
The Difference
Every other SQL-to-MongoDB tool gives you a query object. You still have to wire execution, handle errors, manage connections. StrictDB does all of it.
They translate SQL to a MongoDB query object. Then they hand it to you.
SQL in. Results out. Nothing in between is your problem.
.fixLive Examples
Every example shows the SQL you write, the MongoDB pipeline StrictDB builds,
and what you get back. Use { explain: true } to inspect any query.
const results = await db.sql(` SELECT name, email, created_at FROM users WHERE status = 'active' AND age >= 21 ORDER BY created_at DESC LIMIT 20 `);
[
{ $match: {
status: "active",
age: { $gte: 21 }
}},
{ $project: {
name: 1, email: 1,
created_at: 1, _id: 0
}},
{ $sort: { created_at: -1 }},
{ $limit: 20 }
]
const results = await db.sql(` SELECT u.name, o.total, o.date FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE o.total > 100 LIMIT 50 `);
[
{ $lookup: {
from: "orders",
localField: "id",
foreignField: "user_id",
as: "o"
}},
{ $unwind: "$o" },
{ $match: {
"o.total": { $gt: 100 }
}},
{ $project: {
name: 1,
"o.total": 1,
"o.date": 1, _id: 0
}},
{ $limit: 50 }
]
const results = await db.sql(` SELECT u.name, p.amount FROM users u FULL OUTER JOIN payments p ON u.id = p.user_id LIMIT 100 `);
// Pipeline 1: LEFT JOIN (users -> payments) [{ $lookup: { from: "payments", ... }}, { $unwind: { path: "$p", preserveNullAndEmptyArrays: true }}] // Pipeline 2: anti-join (payments not in users) [{ $lookup: { from: "users", ... }}, { $match: { "u": { $size: 0 }}}] // Results merged with $unionWith
const results = await db.sql(` SELECT name, email FROM users WHERE id IN ( SELECT user_id FROM orders WHERE total > 500 ) LIMIT 25 `);
// Phase 2: subquery resolved FIRST // orders.aggregate([ // { $match: { total: { $gt: 500 }}}, // { $project: { user_id: 1 }} // ]) => [42, 87, 156, ...] // Phase 3: resolved values injected [ { $match: { id: { $in: [42, 87, 156, ...] } }}, { $project: { name: 1, email: 1, _id: 0 }}, { $limit: 25 } ]
const results = await db.sql(` SELECT category, COUNT(*) AS total, AVG(price) AS avg_price FROM products GROUP BY category HAVING COUNT(*) > 10 ORDER BY avg_price DESC LIMIT 20 `);
[
{ $group: {
_id: "$category",
total: { $sum: 1 },
avg_price: { $avg: "$price" }
}},
{ $match: {
total: { $gt: 10 }
}},
{ $project: {
category: "$_id",
total: 1,
avg_price: 1, _id: 0
}},
{ $sort: { avg_price: -1 }},
{ $limit: 20 }
]
const results = await db.sql(` SELECT name, department, salary, RANK() OVER ( PARTITION BY department ORDER BY salary DESC ) AS rank FROM employees LIMIT 50 `);
[
{ $setWindowFields: {
partitionBy: "$department",
sortBy: { salary: -1 },
output: {
rank: {
$rank: {}
}
}
}},
{ $project: {
name: 1, department: 1,
salary: 1, rank: 1,
_id: 0
}},
{ $limit: 50 }
]
const results = await db.sql(` SELECT name, salary, CASE WHEN salary > 100000 THEN 'senior' WHEN salary > 60000 THEN 'mid' ELSE 'junior' END AS level FROM employees LIMIT 50 `);
[
{ $project: {
name: 1,
salary: 1,
level: {
$switch: {
branches: [
{ case: { $gt: ["$salary", 100000]},
then: "senior" },
{ case: { $gt: ["$salary", 60000]},
then: "mid" }
],
default: "junior"
}
},
_id: 0
}},
{ $limit: 50 }
]
const result = await db.sql(` INSERT INTO users (name, email, role) VALUES ('Alice', 'alice@co.io', 'admin') RETURNING id, name, created_at `);
// 1. insertOne executed db.users.insertOne({ name: "Alice", email: "alice@co.io", role: "admin" }) // 2. RETURNING fields fetched // { id: "6614a...", name: "Alice", // created_at: "2026-03-29T..." }
const results = await db.sql( `SELECT * FROM users WHERE role = ? AND age > ? LIMIT 10`, ['admin', 25] );
const results = await db.sql( `SELECT * FROM users WHERE role = $1 AND age > $2 LIMIT 10`, ['admin', 25] );
const plan = await db.sql( `SELECT name FROM users WHERE status = 'active' LIMIT 10`, [], { explain: true } ); // Returns the MongoDB pipeline // without executing it
{
sql: "SELECT name FROM users...",
collection: "users",
operation: "aggregate",
pipeline: [
{ $match: { status: "active" }},
{ $project: { name: 1, _id: 0 }},
{ $limit: 10 }
]
}
Complete Coverage
This is not a subset of SQL. StrictDB supports the full breadth of SQL operations and translates each one to the optimal MongoDB pipeline.
| SQL Construct | Status | MongoDB Translation |
|---|---|---|
| Queries | ||
| SELECT, WHERE, ORDER BY | ✓ | $match, $sort, $project |
| LIMIT, OFFSET | ✓ | $limit, $skip |
| DISTINCT | ✓ | $group |
| LIKE, NOT LIKE | ✓ | $regex |
| BETWEEN | ✓ | $gte + $lte |
| IS NULL, IS NOT NULL | ✓ | $eq: null, $ne: null |
| IN, NOT IN | ✓ | $in, $nin |
| Joins | ||
| INNER JOIN | ✓ | $lookup + $unwind |
| LEFT JOIN | ✓ | $lookup + $unwind (preserveNull) |
| RIGHT JOIN | ✓ | Reversed $lookup |
| FULL OUTER JOIN | ✓ | Two pipelines + $unionWith |
| Aggregation | ||
| GROUP BY, HAVING | ✓ | $group + $match |
| COUNT, SUM, AVG, MIN, MAX | ✓ | $sum, $avg, $min, $max |
| COUNT(DISTINCT col) | ✓ | $addToSet + $size |
| Window Functions | ||
| ROW_NUMBER() | ✓ | $setWindowFields |
| RANK(), DENSE_RANK() | ✓ | $setWindowFields |
| LAG(), LEAD() | ✓ | $setWindowFields + $shift |
| PARTITION BY | ✓ | partitionBy field |
| Subqueries & Expressions | ||
| IN (subquery) | ✓ | Pre-resolved, injected as $in |
| NOT IN (subquery) | ✓ | Pre-resolved, injected as $nin |
| EXISTS (subquery) | ✓ | Pre-resolved boolean check |
| CASE WHEN / THEN / ELSE | ✓ | $switch |
| COALESCE | ✓ | $ifNull |
| NULLIF | ✓ | $cond + $eq |
| Mutations | ||
| INSERT INTO ... VALUES | ✓ | insertOne / insertMany |
| UPDATE ... SET ... WHERE | ✓ | updateMany |
| DELETE FROM ... WHERE | ✓ | deleteMany |
| RETURNING | ✓ | Post-mutation findOne |
| LAST_INSERT_ID() | ✓ | insertedId from receipt |
| Functions | ||
| UPPER, LOWER | ✓ | $toUpper, $toLower |
| CONCAT | ✓ | $concat |
| ROUND, ABS, CEIL, FLOOR | ✓ | $round, $abs, $ceil, $floor |
| LENGTH / CHAR_LENGTH | ✓ | $strLenCP |
| NOW() / CURRENT_TIMESTAMP | ✓ | new Date() |
| Transactions & Parameters | ||
| BEGIN / COMMIT / ROLLBACK | ✓ | MongoDB sessions |
| MySQL ? params | ✓ | Positional binding |
| PostgreSQL $N params | ✓ | Indexed binding |
| { explain: true } | ✓ | Returns pipeline, no execution |
Safety First
SQL Mode inherits every guardrail from StrictDB. Dangerous queries are blocked before they reach MongoDB — and errors tell you exactly how to fix them.
DELETE FROM users would wipe the entire collection
SELECT * FROM logs would return every document
Every error includes a .fix field with the exact corrective action
try { await db.sql(`DELETE FROM users`); } catch (err) { err.code // 'GUARDRAIL_BLOCK' err.fix // 'DELETE without WHERE is blocked. // Add a WHERE clause, or use // confirm: "DELETE_ALL" to override.' err.retryable // false } // AI reads .fix, adds WHERE clause, // retries successfully
One install. Full SQL engine on top of MongoDB. Guardrails, explain mode, and self-correcting errors included.
npm install strictdb