npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

querymongo

v1.8.0

Published

SQL to MongoDB Query Converter CLI

Readme

QueryMongo 🚀

npm version npm downloads License: ISC Last Commit Coverage Status

SQL to MongoDB Query Converter CLI - Convierte queries SQL a MongoDB format de forma simple y poderosa.

📋 Características

✅ Convertir SELECT queries - Con proyecciones, filtros, LIKE, IN y límites
✅ Convertir INSERT queries - Para inserción de documentos únicos o múltiples
✅ Convertir UPDATE queries - Con filtros y actualizaciones en uno o muchos documentos
✅ Convertir DELETE queries - Para eliminación con condiciones complejas
✅ Convertir JOIN queries - INNER JOIN y LEFT JOIN entre colecciones
✅ Soporte para ORDER BY y sorting - Permite ordenar resultados según múltiples campos y direcciones, aplica tanto en queries SELECT como en JOIN
✅ Soporte para agregaciones, GROUP BY y HAVING - COUNT, SUM, AVG, MIN, MAX y filtros de grupos convertidos a $group y $match
✅ CLI interactiva - Modo interactivo para pruebas rápidas
✅ API TypeScript - Para uso como librería en tus proyectos

🏗️ Arquitectura

Proyecto implementado siguiendo principios SOLID, Clean Code y DRY:

src/
├── domain/
│   └── interfaces/
│       └── Converter.ts         # Interface strategy para conversores
├── infrastructure/
│   ├── parser/
│   │   └── sqlParser.ts         # Wrapper sobre node-sql-parser
│   ├── converters/
│   │   ├── SelectConverter.ts   # Estrategia SELECT
│   │   ├── CreateConverter.ts   # Estrategia INSERT
│   │   ├── UpdateConverter.ts   # Estrategia UPDATE
│   │   ├── DeleteConverter.ts   # Estrategia DELETE
│   │   ├── JoinConverter.ts     # Estrategia JOIN
│   │   └── ConverterFactory.ts  # Factory + Strategy Pattern
│   └── utils/
│       └── queryUtils.ts        # Utilidades compartidas (DRY)
├── application/
|   ├── MongooseConverter.ts     # Orquestador específico para Mongoose
│   └── MongoConverter.ts        # Orquestador principal
├── cli/
│   └── main.ts                  # Interfaz CLI
└── index.ts                     # Exports públicos

Patrones Aplicados

  • Strategy Pattern: Cada tipo de query (SELECT, INSERT, etc) es una estrategia diferente
  • Factory Pattern: ConverterFactory selecciona la estrategia correcta
  • Dependency Inversion: Se depende de la interface IConverter, no de implementaciones
  • Single Responsibility: Cada conversor tiene un propósito único

💻 Uso

🚀 Instalación Global - CLI

Para instalar QueryMongo globalmente en tu máquina:

npm i querymongo -g

Luego ejecuta desde cualquier directorio:

querymongo

🛠️ Desarrollo Local

Para trabajar en el desarrollo del proyecto:

git clone https://github.com/rubsuadav/querymongo
cd querymongo
npm install
npm run dev

Esto abrirá la CLI interactiva donde puedes ingresar queries SQL y recibir instantáneamente el equivalente en MongoDB.

📦 Uso como Librería

import { mongoConverter } from "querymongo";

const sql = "SELECT name, email FROM users WHERE age > 18";
const result = mongoConverter.convert(sql);
console.log(result);

📚 Ejemplos de Conversión

SELECT - Casos Básicos

SELECT simple (todos los documentos)

SELECT * FROM users

Salida del Conversor:

{
  "collection": "users",
  "queryType": "find",
  "pipeline": [
    {
      "$match": {}
    },
    {
      "$project": {}
    }
  ]
}

Mongoose Query:

await users.find({}, {});

SELECT con campos específicos

SELECT name, email FROM users

Salida del Conversor:

{
  "collection": "users",
  "queryType": "find",
  "pipeline": [
    {
      "$match": {}
    },
    {
      "$project": {
        "name": 1,
        "email": 1
      }
    }
  ]
}

Mongoose Query:

await users.find({}, { name: 1, email: 1 });

SELECT - Filtros y Condiciones

SELECT con WHERE simple

SELECT * FROM users WHERE status = 'active'

Salida del Conversor:

{
  "collection": "users",
  "queryType": "find",
  "pipeline": [
    {
      "$match": {
        "status": {
          "$eq": "active"
        }
      }
    },
    {
      "$project": {}
    }
  ]
}

Mongoose Query:

await users.find({ status: { $eq: "active" } }, {});

SELECT con comparación

SELECT * FROM products WHERE price > 100

Salida del Conversor:

{
  "collection": "products",
  "queryType": "find",
  "pipeline": [
    {
      "$match": {
        "price": {
          "$gt": 100
        }
      }
    },
    {
      "$project": {}
    }
  ]
}

Mongoose Query:

await products.find({ price: { $gt: 100 } }, {});

SELECT con múltiples condiciones (AND)

SELECT * FROM users WHERE age >= 18 AND status = 'active'

Salida del Conversor:

{
  "collection": "users",
  "queryType": "find",
  "pipeline": [
    {
      "$match": {
        "$and": [
          {
            "age": {
              "$gte": 18
            }
          },
          {
            "status": {
              "$eq": "active"
            }
          }
        ]
      }
    },
    {
      "$project": {}
    }
  ]
}

Mongoose Query:

await users.find(
  { $and: [{ age: { $gte: 18 } }, { status: { $eq: "active" } }] },
  {},
);

SELECT con múltiples condiciones (OR)

SELECT * FROM users WHERE status = 'active' OR status = 'pending'

Salida del Conversor:

{
  "collection": "users",
  "queryType": "find",
  "pipeline": [
    {
      "$match": {
        "$or": [
          {
            "status": {
              "$eq": "active"
            }
          },
          {
            "status": {
              "$eq": "pending"
            }
          }
        ]
      }
    },
    {
      "$project": {}
    }
  ]
}

Mongoose Query:

await users.find(
  { $or: [{ status: { $eq: "active" } }, { status: { $eq: "pending" } }] },
  {},
);

SELECT con LIKE (búsqueda por patrón)

SELECT * FROM users WHERE email LIKE '%@gmail.com'

Salida del Conversor:

{
  "collection": "users",
  "queryType": "find",
  "pipeline": [
    {
      "$match": {
        "email": {
          "$regex": "%@gmail.com"
        }
      }
    },
    {
      "$project": {}
    }
  ]
}

Mongoose Query:

await users.find({ email: { $regex: "%@gmail.com" } }, {});

SELECT con IN

SELECT * FROM products WHERE category IN ('electronics', 'books', 'clothing')

Salida del Conversor:

{
  "collection": "products",
  "queryType": "find",
  "pipeline": [
    {
      "$match": {
        "category": {
          "$in": [
            {
              "type": "single_quote_string",
              "value": "electronics"
            },
            {
              "type": "single_quote_string",
              "value": "books"
            },
            {
              "type": "single_quote_string",
              "value": "clothing"
            }
          ]
        }
      }
    },
    {
      "$project": {}
    }
  ]
}

Mongoose Query:

await products.find(
  { category: { $in: ["electronics", "books", "clothing"] } },
  {},
);

SELECT - Proyecciones y Límites

SELECT con LIMIT

SELECT * FROM users LIMIT 5

Salida del Conversor:

{
  "collection": "users",
  "queryType": "find",
  "pipeline": [
    {
      "$match": {}
    },
    {
      "$project": {}
    },
    {
      "$limit": 5
    }
  ]
}

Mongoose Query:

await users.find({}, {}).limit(5);

SELECT con campos, filtro y límite

SELECT name, age FROM users WHERE age >= 18 LIMIT 10

Salida del Conversor:

{
  "collection": "users",
  "queryType": "aggregation",
  "pipeline": [
    {
      "$match": {
        "age": {
          "$gte": 18
        }
      }
    },
    {
      "$project": {
        "name": 1,
        "age": 1
      }
    },
    {
      "$limit": 10
    }
  ]
}

Mongoose Query:

await users.find({ age: { $gte: 18 } }, { name: 1, age: 1 }).limit(10);

AGGREGATIONS - Agregaciones y GROUP BY

Las funciones SQL COUNT, SUM, AVG, MIN y MAX se convierten en acumuladores de MongoDB dentro de una etapa $group. Cuando no existe GROUP BY, MongoDB usa _id: null y devuelve un único documento con los totales.

Agregaciones sin GROUP BY

SELECT SUM(runtime) AS totalRuntime,
       AVG(runtime) AS averageRuntime,
       MIN(runtime) AS minimumRuntime,
       MAX(runtime) AS maximumRuntime,
       COUNT(*) AS count
FROM movies

Salida del Conversor:

{
  "collection": "movies",
  "queryType": "aggregation",
  "pipeline": [
    {
      "$match": {}
    },
    {
      "$group": {
        "_id": null,
        "totalRuntime": { "$sum": "$runtime" },
        "averageRuntime": { "$avg": "$runtime" },
        "minimumRuntime": { "$min": "$runtime" },
        "maximumRuntime": { "$max": "$runtime" },
        "count": { "$sum": 1 }
      }
    }
  ]
}

Mongoose Query:

await movies.aggregate([
  {
    $match: {},
  },
  {
    $group: {
      _id: null,
      totalRuntime: { $sum: "$runtime" },
      averageRuntime: { $avg: "$runtime" },
      minimumRuntime: { $min: "$runtime" },
      maximumRuntime: { $max: "$runtime" },
      count: { $sum: 1 },
    },
  },
]);

GROUP BY con COUNT

SELECT category, COUNT(*) AS count
FROM products
GROUP BY category

Salida del Conversor:

{
  "collection": "products",
  "queryType": "aggregation",
  "pipeline": [
    { "$match": {} },
    {
      "$group": {
        "_id": "$category",
        "count": { "$sum": 1 }
      }
    },
    {
      "$project": {
        "_id": 0,
        "category": "$_id",
        "count": 1
      }
    }
  ]
}

Mongoose Query:

await products.aggregate([
  { $match: {} },
  {
    $group: {
      _id: "$category",
      count: { $sum: 1 },
    },
  },
  {
    $project: {
      _id: 0,
      category: "$_id",
      count: 1,
    },
  },
]);

GROUP BY con WHERE y ORDER BY

SELECT category,
       SUM(price) AS total,
       AVG(price) AS average
FROM products
WHERE active = true
GROUP BY category
ORDER BY total DESC

Salida del Conversor:

{
  "collection": "products",
  "queryType": "aggregation",
  "pipeline": [
    { "$match": { "active": { "$eq": true } } },
    {
      "$group": {
        "_id": "$category",
        "total": { "$sum": "$price" },
        "average": { "$avg": "$price" }
      }
    },
    {
      "$project": {
        "_id": 0,
        "category": "$_id",
        "total": 1,
        "average": 1
      }
    },
    { "$sort": { "total": -1 } }
  ]
}

Mongoose Query:

await products.aggregate([
  { $match: { active: { $eq: true } } },
  {
    $group: {
      _id: "$category",
      total: { $sum: "$price" },
      average: { $avg: "$price" },
    },
  },
  {
    $project: {
      _id: 0,
      category: "$_id",
      total: 1,
      average: 1,
    },
  },
  { $sort: { total: -1 } },
]);

GROUP BY con HAVING por alias

SELECT category, COUNT(*) AS total
FROM products
GROUP BY category
HAVING total > 5
ORDER BY total DESC

WHERE filtra documentos antes de agrupar. HAVING filtra los grupos después de calcular sus agregaciones.

Salida del Conversor:

{
  "collection": "products",
  "queryType": "aggregation",
  "pipeline": [
    { "$match": {} },
    {
      "$group": {
        "_id": "$category",
        "total": { "$sum": 1 }
      }
    },
    {
      "$project": {
        "_id": 0,
        "category": "$_id",
        "total": 1
      }
    },
    {
      "$match": {
        "total": { "$gt": 5 }
      }
    },
    { "$sort": { "total": -1 } }
  ]
}

Mongoose Query:

await products.aggregate([
  { $match: {} },
  {
    $group: {
      _id: "$category",
      total: { $sum: 1 },
    },
  },
  {
    $project: {
      _id: 0,
      category: "$_id",
      total: 1,
    },
  },
  { $match: { total: { $gt: 5 } } },
  { $sort: { total: -1 } },
]);

HAVING con expresión agregada

SELECT category, SUM(price) AS total
FROM products
GROUP BY category
HAVING SUM(price) >= 100

La expresión SUM(price) se resuelve mediante el alias total generado en el $group:

{
  "$match": {
    "total": {
      "$gte": 100
    }
  }
}

JOIN con GROUP BY y HAVING

SELECT u.country, COUNT(*) AS total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.country
HAVING total >= 10
ORDER BY total DESC

El pipeline mantiene el orden relacional correcto:

$lookup -> $unwind -> $group -> $project -> $match (HAVING) -> $sort

Salida del Conversor:

{
  "collection": "users",
  "queryType": "find",
  "pipeline": [
    {
      "$lookup": {
        "from": "orders",
        "localField": "id",
        "foreignField": "user_id",
        "as": "o"
      }
    },
    {
      "$unwind": {
        "path": "$o",
        "preserveNullAndEmptyArrays": true
      }
    },
    {
      "$group": {
        "_id": "$country",
        "total": { "$sum": 1 }
      }
    },
    {
      "$project": {
        "_id": 0,
        "country": "$_id",
        "total": 1
      }
    },
    { "$match": { "total": { "$gte": 10 } } },
    { "$sort": { "total": -1 } }
  ]
}

JOIN con GROUP BY

SELECT u.country, COUNT(*) AS total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.country
ORDER BY total DESC

Salida del Conversor:

{
  "collection": "users",
  "queryType": "find",
  "pipeline": [
    {
      "$lookup": {
        "from": "orders",
        "localField": "id",
        "foreignField": "user_id",
        "as": "o"
      }
    },
    {
      "$unwind": {
        "path": "$o",
        "preserveNullAndEmptyArrays": true
      }
    },
    {
      "$group": {
        "_id": "$country",
        "total": { "$sum": 1 }
      }
    },
    {
      "$project": {
        "_id": 0,
        "country": "$_id",
        "total": 1
      }
    },
    { "$sort": { "total": -1 } }
  ]
}

Mongoose Query:

await users.aggregate([
  {
    $lookup: {
      from: "orders",
      localField: "id",
      foreignField: "user_id",
      as: "o",
    },
  },
  {
    $unwind: {
      path: "$o",
      preserveNullAndEmptyArrays: true,
    },
  },
  {
    $group: {
      _id: "$country",
      total: { $sum: 1 },
    },
  },
  {
    $project: {
      _id: 0,
      country: "$_id",
      total: 1,
    },
  },
  { $sort: { total: -1 } },
]);

INSERT - Inserción de Documentos

INSERT simple

INSERT INTO users (name, email, age) VALUES ('John Doe', '[email protected]', 30)

Salida del Conversor:

{
  "collection": "users",
  "operation": "insertOne",
  "documents": {
    "name": "John Doe",
    "email": "[email protected]",
    "age": 30
  }
}

Mongoose Query:

await users.insertOne({ name: "John Doe", email: "[email protected]", age: 30 });

INSERT múltiple en una sola operación

INSERT INTO users (name, email) VALUES
  ('Alice', '[email protected]'),
  ('Bob', '[email protected]'),
  ('Charlie', '[email protected]')

Salida del Conversor:

{
  "collection": "users",
  "operation": "insertMany",
  "documents": [
    { "name": "Alice", "email": "[email protected]" },
    { "name": "Bob", "email": "[email protected]" },
    { "name": "Charlie", "email": "[email protected]" }
  ]
}

Mongoose Query:

await users.insertMany([
  { name: "Alice", email: "[email protected]" },
  { name: "Bob", email: "[email protected]" },
  { name: "Charlie", email: "[email protected]" },
]);

INSERT con valores numéricos y tipos diversos

INSERT INTO products (name, price, quantity, rating)
VALUES ('Laptop', 999.99, 5, 4.5)

Salida del Conversor:

{
  "collection": "products",
  "operation": "insertOne",
  "documents": {
    "name": "Laptop",
    "price": 999.99,
    "quantity": 5,
    "rating": 4.5
  }
}

Mongoose Query:

await products.insertOne({
  name: "Laptop",
  price: 999.99,
  quantity: 5,
  rating: 4.5,
});

UPDATE - Actualización de Documentos

UPDATE con WHERE (updateOne)

UPDATE users SET status = 'inactive' WHERE id = 1

Salida del Conversor:

{
  "collection": "users",
  "operation": "updateOne",
  "filter": {
    "id": {
      "$eq": 1
    }
  },
  "update": {
    "$set": {
      "status": "inactive"
    }
  }
}

Mongoose Query:

await users.updateOne({ id: { $eq: 1 } }, { $set: { status: "inactive" } });

UPDATE múltiples campos

UPDATE users SET status = 'active', last_login = '2026-09-21' WHERE email = '[email protected]'

Salida del Conversor:

{
  "collection": "users",
  "operation": "updateOne",
  "filter": {
    "email": {
      "$eq": "[email protected]"
    }
  },
  "update": {
    "$set": {
      "status": "active",
      "last_login": "2026-09-21"
    }
  }
}

Mongoose Query:

await users.updateOne(
  { email: { $eq: "[email protected]" } },
  { $set: { status: "active", last_login: "2026-09-21" } },
);

UPDATE múltiples documentos (updateMany)

UPDATE products SET discount = 10 WHERE price > 500

Salida del Conversor:

{
  "collection": "products",
  "operation": "updateMany",
  "filter": { "price": { "$gt": 500 } },
  "update": { "$set": { "discount": 10 } }
}

Mongoose Query:

await products.updateMany({ price: { $gt: 500 } }, { $set: { discount: 10 } });

UPDATE todos los documentos

UPDATE users SET status = 'archived'

Salida del Conversor:

{
  "collection": "users",
  "operation": "updateMany",
  "filter": {},
  "update": { "$set": { "status": "archived" } }
}

Mongoose Query:

await users.updateMany({}, { $set: { status: "archived" } });

DELETE - Eliminación de Documentos

DELETE con WHERE (deleteOne)

DELETE FROM users WHERE id = 1

Salida del Conversor:

{
  "collection": "users",
  "operation": "deleteOne",
  "filter": {
    "id": {
      "$eq": 1
    }
  }
}

Mongoose Query:

await users.deleteOne({ id: { $eq: 1 } });

DELETE múltiples documentos

DELETE FROM users WHERE age < 18

Salida del Conversor:

{
  "collection": "users",
  "operation": "deleteMany",
  "filter": { "age": { "$lt": 18 } }
}

Mongoose Query:

await users.deleteMany({ age: { $lt: 18 } });

DELETE con múltiples condiciones (AND)

DELETE FROM users WHERE status = 'inactive' AND created_at < '2023-01-01'

Salida del Conversor:

{
  "collection": "users",
  "operation": "deleteMany",
  "filter": {
    "$and": [
      {
        "status": {
          "$eq": "inactive"
        }
      },
      {
        "created_at": {
          "$lt": "2023-01-01"
        }
      }
    ]
  }
}

Mongoose Query:

await users.deleteMany({
  $and: [
    { status: { $eq: "inactive" } },
    { created_at: { $lt: "2023-01-01" } },
  ],
});

DELETE con IN

DELETE FROM products WHERE id IN (1, 5, 10)

Salida del Conversor:

{
  "collection": "products",
  "operation": "deleteMany",
  "filter": { "id": { "$in": [1, 5, 10] } }
}

Mongoose Query:

await products.deleteMany({ id: { $in: [1, 5, 10] } });

⚠️ DELETE todos los documentos

DELETE FROM users

Salida del Conversor:

{
  "collection": "users",
  "operation": "deleteMany",
  "filter": {}
}

Mongoose Query:

await users.deleteMany({});

JOIN - Uniones entre Colecciones

INNER JOIN simple

SELECT u.name, o.order_id FROM users u INNER JOIN orders o ON u.id = o.user_id

Salida del Conversor:

{
  "collection": "users",
  "pipeline": [
    {
      "$lookup": {
        "from": "orders",
        "localField": "id",
        "foreignField": "user_id",
        "as": "o"
      }
    },
    {
      "$unwind": {
        "path": "$o",
        "preserveNullAndEmptyArrays": false
      }
    },
    {
      "$project": {
        "name": 1,
        "order_id": 1
      }
    }
  ]
}

Mongoose Query:

await users.aggregate([
  {
    $lookup: {
      from: "orders",
      localField: "id",
      foreignField: "user_id",
      as: "o",
    },
  },
  {
    $unwind: {
      path: "$o",
      preserveNullAndEmptyArrays: false,
    },
  },
  {
    $project: {
      name: 1,
      order_id: 1,
    },
  },
]);

LEFT JOIN

SELECT u.name, o.order_id FROM users u LEFT JOIN orders o ON u.id = o.user_id

Salida del Conversor:

{
  "collection": "users",
  "pipeline": [
    {
      "$lookup": {
        "from": "orders",
        "localField": "id",
        "foreignField": "user_id",
        "as": "o"
      }
    },
    {
      "$unwind": {
        "path": "$o",
        "preserveNullAndEmptyArrays": true
      }
    },
    {
      "$project": {
        "name": 1,
        "order_id": 1
      }
    }
  ]
}

Mongoose Query:

await users.aggregate([
  {
    $lookup: {
      from: "orders",
      localField: "id",
      foreignField: "user_id",
      as: "o",
    },
  },
  {
    $unwind: {
      path: "$o",
      preserveNullAndEmptyArrays: true,
    },
  },
  {
    $project: {
      name: 1,
      order_id: 1,
    },
  },
]);

🧪 Testing

npm test

Cobertura: 100% en líneas y funciones.

🔧 Operadores Soportados

| SQL | MongoDB | Descripción | | ----------- | --------------- | -------------------------------------- | | = | $eq | Igualdad | | != o <> | $ne | No igual | | < | $lt | Menor que | | > | $gt | Mayor que | | <= | $lte | Menor o igual | | >= | $gte | Mayor o igual | | WHERE | $match | Condición de filtrado | | LIKE | $regex | Búsqueda por patrón | | IN | $in | Dentro de lista | | NOT IN | $nin | Fuera de lista | | AND | $and | Operador lógico Y | | OR | $or | Operador lógico O | | JOIN | $lookup | Unión entre colecciones | | ORDER BY | $sort | Ordenamiento de resultados | | BETWEEN | $gte y $lte | Rango de valores | | LIMIT | $limit | Límite de resultados | | COUNT | $sum: 1 | Conteo de documentos | | SUM | $sum | Suma de valores | | AVG | $avg | Promedio de valores | | MIN | $min | Valor mínimo | | MAX | $max | Valor máximo | | GROUP BY | $group | Agrupamiento de resultados | | HAVING | $match | Filtrado de grupos después de $group |

📦 Dependencias

  • node-sql-parser: Parseo de queries SQL
  • lodash: Utilidades funcionales
  • chalk: Colores en CLI
  • commander: Interfaz CLI
  • typescript: Compilador TypeScript

🎯 Próximas Mejoras

  • [ ] OFFSET clause (pagination)
  • [ ] Subqueries en WHERE

📄 Licencia

ISC