Skip to content

CRUD ​

CRUD (Create, Read, Update, Delete) es lo que hace cualquier aplicación: dar de alta, consultar, editar y borrar. En esta lección cargarás los datos de ejemplo de la tienda que usaremos el resto del curso y dominarás los métodos de MongoDB para cada operación: insertOne/insertMany, find/findOne con proyecciones, updateOne/updateMany con sus operadores, upsert, replaceOne y deleteOne/deleteMany.

Concepto ​

CRUDSQLMongoDB
CreateINSERTinsertOne(), insertMany()
ReadSELECTfind(), findOne(), countDocuments()
UpdateUPDATEupdateOne(), updateMany(), replaceOne(), findOneAndUpdate()
DeleteDELETEdeleteOne(), deleteMany(), findOneAndDelete()

Todos se llaman sobre una colección: db.<coleccion>.<metodo>(...). Casi todos reciben un filtro (qué documentos) y, según el caso, una actualización u opciones.

Datos de ejemplo: la tienda ​

Guarda este script como semilla.js y ejecútalo con mongosh "TU_URI/tienda" semilla.js (o pégalo en mongosh tras use tienda). Usamos _id numéricos para que los resultados sean fáciles de leer.

js
// semilla.js — datos de ejemplo de la tienda (curso de MongoDB)
db = db.getSiblingDB("tienda");

db.productos.drop();
db.clientes.drop();
db.pedidos.drop();

db.productos.insertMany([
  { _id: 1, nombre: "Audífonos Bluetooth", categoria: "Electrónica", precio: 899, stock: 25,
    etiquetas: ["audio", "inalambrico"], atributos: { color: "negro", bateriaHoras: 30 } },
  { _id: 2, nombre: "Teclado mecánico", categoria: "Electrónica", precio: 1250, stock: 10,
    etiquetas: ["gamer", "oficina"], atributos: { color: "blanco", switches: "red" } },
  { _id: 3, nombre: "Monitor 24 pulgadas", categoria: "Electrónica", precio: 3499, stock: 5,
    etiquetas: ["gamer", "oficina"], atributos: { pulgadas: 24, hz: 144 } },
  { _id: 4, nombre: "Libro Aprende SQL", categoria: "Libros", precio: 349, stock: 40,
    etiquetas: ["programacion"], atributos: { autor: "Equipo Academia", paginas: 320 } },
  { _id: 5, nombre: "Cien años de soledad", categoria: "Libros", precio: 289, stock: 30,
    etiquetas: ["novela", "clasico"], atributos: { autor: "Gabriel García Márquez", paginas: 471 } },
  { _id: 6, nombre: "Lámpara de escritorio", categoria: "Hogar", precio: 459, stock: 15,
    etiquetas: ["oficina"], atributos: { color: "negro", watts: 9 } },
  { _id: 7, nombre: "Taza térmica", categoria: "Hogar", precio: 199, stock: 50,
    etiquetas: ["cocina"], atributos: { capacidadMl: 450 } },
  { _id: 8, nombre: "Cable USB-C", precio: 99, stock: 100, etiquetas: [] }   // sin categoría
]);

db.clientes.insertMany([
  { _id: 1, nombre: "Ana López", email: "ana@correo.com",
    direccion: { ciudad: "Ciudad de México", cp: "06600" }, registro: new Date("2024-01-15") },
  { _id: 2, nombre: "Carlos Ruiz", email: "carlos@correo.com",
    direccion: { ciudad: "Guadalajara", cp: "44100" }, registro: new Date("2024-02-03") },
  { _id: 3, nombre: "María Pérez", email: "maria@correo.com",
    direccion: { ciudad: "Monterrey", cp: "64000" }, registro: new Date("2024-02-20") },
  { _id: 4, nombre: "Jorge Díaz", email: "jorge@correo.com",
    direccion: { ciudad: "Ciudad de México", cp: "03100" }, registro: new Date("2024-03-11") },
  { _id: 5, nombre: "Lucía Gómez", email: "lucia@correo.com",
    direccion: { ciudad: "Puebla", cp: "72000" }, registro: new Date("2024-04-02") }   // sin pedidos
]);

db.pedidos.insertMany([
  { _id: 1, clienteId: 1, fecha: new Date("2024-05-02"), estado: "entregado",
    items: [ { productoId: 1, nombre: "Audífonos Bluetooth", cantidad: 1, precio: 899 },
             { productoId: 4, nombre: "Libro Aprende SQL", cantidad: 2, precio: 349 } ],
    total: 1597 },
  { _id: 2, clienteId: 2, fecha: new Date("2024-05-10"), estado: "entregado",
    items: [ { productoId: 2, nombre: "Teclado mecánico", cantidad: 1, precio: 1250 },
             { productoId: 7, nombre: "Taza térmica", cantidad: 3, precio: 199 } ],
    total: 1847 },
  { _id: 3, clienteId: 1, fecha: new Date("2024-06-01"), estado: "enviado",
    items: [ { productoId: 3, nombre: "Monitor 24 pulgadas", cantidad: 1, precio: 3499 } ],
    total: 3499 },
  { _id: 4, clienteId: 3, fecha: new Date("2024-06-15"), estado: "pendiente",
    items: [ { productoId: 5, nombre: "Cien años de soledad", cantidad: 1, precio: 289 },
             { productoId: 6, nombre: "Lámpara de escritorio", cantidad: 1, precio: 459 },
             { productoId: 7, nombre: "Taza térmica", cantidad: 2, precio: 199 } ],
    total: 1146 },
  { _id: 5, clienteId: 4, fecha: new Date("2024-06-20"), estado: "cancelado",
    items: [ { productoId: 1, nombre: "Audífonos Bluetooth", cantidad: 1, precio: 899 } ],
    total: 899 }
]);

print("Productos: " + db.productos.countDocuments());   // 8
print("Clientes:  " + db.clientes.countDocuments());    // 5
print("Pedidos:   " + db.pedidos.countDocuments());     // 5

Fíjate en el diseño: cada pedido embebe sus items con una copia del nombre y precio del producto al momento de la compra (igual que precio_unitario en el curso de SQL), y guarda el total calculado para listar pedidos rápido.

Create: insertar ​

insertOne ​

js
db.productos.insertOne({
  nombre: "Bocina portátil",
  categoria: "Electrónica",
  precio: 749,
  stock: 12,
  etiquetas: ["audio", "inalambrico"],
  atributos: { resistenteAgua: true }
})
js
{ acknowledged: true, insertedId: ObjectId("66a2b7f1c4e5d6a7b8c9d0e1") }

Como no indicamos _id, se generó un ObjectId. (Puedes convivir con _id numéricos y ObjectId en la misma colección, aunque en un proyecto real conviene ser consistente.)

insertMany ​

js
db.productos.insertMany([
  { _id: 9, nombre: "Balón de fútbol", categoria: "Deportes", precio: 399, stock: 20, etiquetas: ["futbol"] },
  { _id: 10, nombre: "Tapete de yoga", categoria: "Deportes", precio: 349, stock: 18, etiquetas: ["yoga"] }
])
js
{ acknowledged: true, insertedIds: { '0': 9, '1': 10 } }

ordered: true por defecto

Si un documento de insertMany falla (por ejemplo, _id duplicado), por defecto se detiene ahí: los anteriores quedan insertados y los siguientes no. Con { ordered: false } intenta insertar todos y reporta los errores al final. No es una transacción: para "todo o nada" necesitas transacciones (Índices y modelado).

Read: consultar ​

find y findOne ​

js
db.productos.find()                          // todos
db.productos.find({ categoria: "Libros" })   // filtro por igualdad
db.productos.findOne({ _id: 3 })             // uno (o null)

find devuelve un cursor (los resultados se traen por lotes); en mongosh se muestran de 20 en 20.

El filtro es un documento: cada campo es una condición y todas deben cumplirse (es un AND implícito):

js
db.productos.find({ categoria: "Electrónica", stock: 10 })
// [ { _id: 2, nombre: "Teclado mecánico", ... } ]

db.clientes.find({ "direccion.ciudad": "Ciudad de México" })
// Ana López y Jorge Díaz

Proyección: elegir campos ​

El segundo argumento de find indica qué campos traer (1 incluir, 0 excluir):

js
db.productos.find({ categoria: "Hogar" }, { nombre: 1, precio: 1 })
js
[
  { _id: 6, nombre: "Lámpara de escritorio", precio: 459 },
  { _id: 7, nombre: "Taza térmica", precio: 199 }
]
js
// _id se incluye siempre, salvo que lo excluyas
db.productos.find({ categoria: "Hogar" }, { _id: 0, nombre: 1, precio: 1 })

// Excluir campos pesados
db.productos.find({}, { atributos: 0, etiquetas: 0 })

// Subcampos
db.clientes.find({}, { nombre: 1, "direccion.ciudad": 1, _id: 0 })

No puedes mezclar inclusiones y exclusiones (salvo _id: 0).

sort, limit y skip ​

js
// Los 3 productos más caros
db.productos.find({}, { nombre: 1, precio: 1, _id: 0 }).sort({ precio: -1 }).limit(3)
js
[
  { nombre: "Monitor 24 pulgadas", precio: 3499 },
  { nombre: "Teclado mecánico", precio: 1250 },
  { nombre: "Audífonos Bluetooth", precio: 899 }
]
MétodoSQLUso
.sort({ campo: 1 })ORDER BY campo ASC-1 = descendente; varios campos = desempate
.limit(n)LIMIT nMáximo de documentos
.skip(n)OFFSET nSaltar documentos (paginación)
js
// Página 2 de 3 en 3, ordenado por precio
db.productos.find().sort({ precio: 1 }).skip(3).limit(3)

Contar ​

js
db.productos.countDocuments()                          // 8 (o más si insertaste)
db.productos.countDocuments({ categoria: "Libros" })   // 2
db.productos.estimatedDocumentCount()                  // rápido, sin filtro (usa metadatos)
db.pedidos.distinct("estado")                          // valores únicos de un campo
// [ "cancelado", "entregado", "enviado", "pendiente" ]

Update: actualizar ​

js
db.coleccion.updateOne(filtro, actualizacion, opciones)
db.coleccion.updateMany(filtro, actualizacion, opciones)

La actualización usa operadores. Sin ellos, estarías reemplazando el documento.

$set y $unset ​

js
// Cambiar o agregar campos
db.productos.updateOne(
  { _id: 1 },
  { $set: { precio: 849, "atributos.color": "azul", enOferta: true } }
)
// { acknowledged: true, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }

// Eliminar un campo
db.productos.updateOne({ _id: 1 }, { $unset: { enOferta: "" } })

matchedCount = documentos que cumplieron el filtro; modifiedCount = los que realmente cambiaron (si ya tenían ese valor, no cuenta).

$inc y $mul ​

js
// Se vendieron 2 tazas: restar stock (¡atómico, sin leer antes!)
db.productos.updateOne({ _id: 7 }, { $inc: { stock: -2 } })

// Subir 10 % todos los productos de Electrónica
db.productos.updateMany({ categoria: "Electrónica" }, { $mul: { precio: 1.10 } })

Atomicidad en un documento

Toda operación sobre un documento es atómica. $inc resuelve el problema de "dos usuarios compran a la vez" sin leer y escribir por separado. Para no vender sin stock, combina filtro y operador:

js
db.productos.updateOne(
  { _id: 3, stock: { $gte: 2 } },   // solo si hay al menos 2
  { $inc: { stock: -2 } }
)
// matchedCount: 0 → no había stock suficiente

Operadores de arrays: $push, $addToSet, $pull, $pop ​

js
// Agregar una etiqueta (permite duplicados)
db.productos.updateOne({ _id: 6 }, { $push: { etiquetas: "led" } })

// Agregar solo si no existe
db.productos.updateOne({ _id: 6 }, { $addToSet: { etiquetas: "oficina" } })   // ya estaba: no cambia

// Agregar varios
db.productos.updateOne({ _id: 6 }, { $push: { etiquetas: { $each: ["estudio", "lectura"] } } })

// Quitar todas las coincidencias
db.productos.updateOne({ _id: 6 }, { $pull: { etiquetas: "estudio" } })

// Quitar el último (1) o el primero (-1)
db.productos.updateOne({ _id: 6 }, { $pop: { etiquetas: 1 } })

Actualizar un elemento dentro de un array de subdocumentos:

js
// $ = el primer elemento que coincidió con el filtro
db.pedidos.updateOne(
  { _id: 4, "items.productoId": 7 },
  { $set: { "items.$.cantidad": 3 } }
)

// $[] = todos los elementos;  $[x] con arrayFilters = los que cumplan una condición
db.pedidos.updateOne(
  { _id: 4 },
  { $mul: { "items.$[it].precio": 0.9 } },
  { arrayFilters: [ { "it.precio": { $gt: 300 } } ] }
)

Otros operadores útiles: $rename (renombrar campo), $min/$max (actualiza solo si el valor nuevo es menor/mayor), $currentDate (pone la fecha actual).

js
db.productos.updateMany({}, { $currentDate: { actualizadoEn: true } })

Upsert: actualizar o insertar ​

js
// Si existe el producto con ese nombre, actualiza el stock; si no, lo crea
db.productos.updateOne(
  { nombre: "Mochila escolar" },
  { $set: { precio: 650, categoria: "Hogar" }, $inc: { stock: 10 } },
  { upsert: true }
)
// { matchedCount: 0, modifiedCount: 0, upsertedCount: 1, upsertedId: ObjectId("...") }

$setOnInsert asigna valores solo cuando se crea el documento:

js
db.visitas.updateOne(
  { pagina: "/productos/1" },
  { $inc: { conteo: 1 }, $setOnInsert: { primeraVisita: new Date() } },
  { upsert: true }
)

replaceOne ​

Sustituye el documento completo (excepto _id):

js
db.productos.replaceOne(
  { _id: 10 },
  { nombre: "Tapete de yoga premium", categoria: "Deportes", precio: 499, stock: 8 }
)
// El campo "etiquetas" desaparece porque no está en el documento nuevo

Error común: olvidar el operador

js
db.productos.updateOne({ _id: 2 }, { precio: 1199 })
// MongoInvalidArgumentError: Update document requires atomic operators

Los drivers modernos lo impiden, pero en versiones viejas (o con replaceOne) borrabas todos los demás campos. Para cambiar un campo usa siempre $set.

findOneAndUpdate ​

Actualiza y devuelve el documento (antes o después del cambio):

js
db.productos.findOneAndUpdate(
  { _id: 2, stock: { $gt: 0 } },
  { $inc: { stock: -1 } },
  { returnDocument: "after", projection: { nombre: 1, stock: 1 } }
)
// { _id: 2, nombre: "Teclado mecánico", stock: 9 }

Muy útil para contadores (folios de factura, turnos) y colas de trabajo.

Delete: borrar ​

js
db.productos.deleteOne({ _id: 10 })
// { acknowledged: true, deletedCount: 1 }

db.pedidos.deleteMany({ estado: "cancelado" })
// { acknowledged: true, deletedCount: 1 }

const borrado = db.productos.findOneAndDelete({ _id: 9 })   // devuelve el documento borrado

deleteMany({})

db.productos.deleteMany({}) borra todos los documentos de la colección (el filtro vacío coincide con todo), igual que un updateMany({}, ...) modifica todo. Antes de un deleteMany o updateMany importante, ejecuta un find o countDocuments con el mismo filtro y revisa el resultado.

Borrado lógico ​

Igual que en SQL, en muchos sistemas se prefiere marcar en lugar de borrar:

js
db.productos.updateOne({ _id: 8 }, { $set: { eliminadoEn: new Date() } })
db.productos.find({ eliminadoEn: { $exists: false } })   // solo activos

Resumen visual ​

txt
insertOne / insertMany  ──►  { documento }
find(filtro, proyección).sort().skip().limit()  ──►  cursor de documentos
updateOne / updateMany(filtro, { $set | $inc | $push | $pull | ... }, { upsert })
replaceOne(filtro, documentoCompleto)
deleteOne / deleteMany(filtro)
findOneAndUpdate / findOneAndDelete  ──►  devuelven el documento

Ejercicios ​

Vuelve a ejecutar semilla.js antes de empezar.

  1. Muestra nombre y precio (sin _id) de los productos de Electrónica, del más barato al más caro.
  2. Agrega el cliente 6: Pedro Sánchez, pedro@correo.com, Mérida, CP 97000, registrado hoy.
  3. Agrega la etiqueta "oferta" a todos los productos de la categoría Libros sin crear duplicados.
  4. El pedido 4 se envió: cambia su estado a "enviado" y agrega el campo enviadoEn con la fecha actual.
  5. Descuenta 1 del stock del Monitor solo si hay existencias, y devuelve el documento actualizado.
  6. Asigna la categoría "Electrónica" a los productos que no tienen categoría.
Ver solución
js
// 1
db.productos.find({ categoria: "Electrónica" }, { _id: 0, nombre: 1, precio: 1 }).sort({ precio: 1 })
// Audífonos 899, Teclado 1250, Monitor 3499

// 2
db.clientes.insertOne({
  _id: 6,
  nombre: "Pedro Sánchez",
  email: "pedro@correo.com",
  direccion: { ciudad: "Mérida", cp: "97000" },
  registro: new Date()
})

// 3
db.productos.updateMany({ categoria: "Libros" }, { $addToSet: { etiquetas: "oferta" } })
// matchedCount: 2, modifiedCount: 2

// 4
db.pedidos.updateOne({ _id: 4 }, { $set: { estado: "enviado" }, $currentDate: { enviadoEn: true } })

// 5
db.productos.findOneAndUpdate(
  { _id: 3, stock: { $gt: 0 } },
  { $inc: { stock: -1 } },
  { returnDocument: "after" }
)
// stock: 4

// 6
db.productos.updateMany({ categoria: { $exists: false } }, { $set: { categoria: "Electrónica" } })
// matchedCount: 1 (Cable USB-C)

Resumen ​

  • Create: insertOne / insertMany; si no das _id, se genera un ObjectId.
  • Read: find(filtro, proyección) con sort, skip, limit; findOne, countDocuments, distinct.
  • Update: siempre con operadores ($set, $unset, $inc, $push, $addToSet, $pull...); upsert crea si no existe.
  • Las operaciones sobre un documento son atómicas: usa $inc con filtro de stock para evitar sobreventas.
  • Delete: deleteOne / deleteMany; cuidado con el filtro vacío {}.

Academia Web — aprende, copia y construye.