Terceros de pasos en Node.js (MongoDB)

En este articulo veremos, como usar una DB real con mongo atlas

Esta es la parte 3, Parte 2 

1. MongoDB atlas

Creamos una cuenta, y creamos un servidor gratuito. 

Nos pedirá crear un usuario y clave para usar al servidor.

Luego nos dara una url para usar y conectarnos.

 

2. Conectarse al servidor

Solamente tenemos que traer la librería, la contraseña y con este código nos conectamos:

 
const mongoose = require('mongoose');
const password = require("./clave.js");

const url = `mongodb+srv://usuario:${password}@clusterEjemplo.aranb.mongodb.net/?retryWrites=true&w=majority&appName=Cluster8000`;
mongoose.set('strictQuery',false);

// Conectarse a la DB de mongo
mongoose.connect(url)
.then(() => { console.log("Conexion a la DB");
})
.catch((err) => { console.error(`ERROR: ${err}`);
});
 

 Las primeras lineas son nuestras variables...

Para conectarnos usamos mongoose, la url, y usamos el método 'then' para dar un mensaje de OK. Y también 'catch' para los errores.


Inciso: Ejemplo GET para demostrar que funciona

Con el siguiente código veremos una demostración del servidor activo:

 
// Obtener todo lo de la DB
Note.find({}).then((res) => {
  console.log(res);
  mongoose.connection.close();
}).catch((err) => { console.error("No se obtuvo nada"); });
 

Note se explica mas adelante. 

Pero en resumen, con el método 'find({})' obtenemos todos los elementos de nuestro servidor. Esto lo podemos ver en la pagina en: nuestro proyecto/el servidor/collections.

 

3.  Esquemas y "clases"

El 'schema' es como una plantilla la cual define el como va el contenido. (Esto es porque la base de datos no relacional no es muy rígida, y se puede hacer cualquier cosa si no se tiene un orden, lo cual es esquema evita) y la crearemos asi:

 
// Un Schema: define los tipos de datos que llevara
// El ejemplo tiene un content, y un important
const noteSchema = new mongoose.Schema({
  content: String,
  important: Boolean,
});

 En este caso, vemos que lo llamamos note, y se compone de un texto y un booleano.


El modelo, es un elemento el cual tienen un nombre y usa un esquema (el cual define las reglas de su contenido) y se crea así:

 
const Note = mongoose.model('Note', noteSchema);
 


Una instancia del modelo, la cual va a ser un elemento que llegara al servidor con reglas definidas. Para esto haremos:


const note = new Note({
  content: 'Nuevo comienzo',
  important: true,
})
 

Importante: es que los esquemas y modelos, por buenas practicas, deben ir en una carpeta diferente, para luego ser importados. 


4. Operaciones HTTP

Es mas fácil. Solamente  tenemos que hacer pequeños cambios al código. 

Antes de empezar. en los siguientes ejemplos usaremos otro esquema y modelo. código:

// Un Schema: define los tipos de datos que llevara
// El ejemplo tiene un content, y un important
const personSchema = new mongoose.Schema({
  id: String,
  name: String,
  number: String,
});

/* Definir el toJson
    Eliminamos las propiedades por default como _id y __v
    Y creamos la propiedad id
*/
personSchema.set("toJSON", {
  transform: (document, returnedObject) => {
    returnedObject.id = returnedObject._id
    delete returnedObject._id
    delete returnedObject.__v
  }
})

// Un modelo: crea una "clase" la cual usa un nombre (en singular), y una serie de reglas: el schema
const Person = mongoose.model('Person', personSchema);

module.exports = Person;
 

Algo a destacar es el 'toJSON' se modifica para cambiar las propiedades que estaran en la DB.

4.1 GET

Obtendremos todos los objetos de la base de datos:

 
app.get("/api/persons", (req, res) => {
    console.log("GET personas");
    Person.find({})
        .then((persons) => { res.status(200).json(persons) })
        .catch((err) => { console.log(err); res.status(404).end("No hay objetos") });
});

 Lo nuevo. Es que usamos el modelo con 'find({})' para obtener todas las instancias de este modelo.

Algo que sera común, es el uso de 'then' y 'catch', ya que es una acción asincronica. 

En el 'then' obtenemos todos los resultados y los mostramos.

En el 'catch'  mostraremos un error.

 

4.2 POST

Para crear una instancia nueva, haremos:

 
app.post("/api/persons", (req, res, next) => {
    let nuevo = req.body;

    if(nuevo.name != null || nuevo.number != null) {

        const persona = new Person({
            name: nuevo.name,
            number: nuevo.number,
        })

        persona.save()
            .then((personaGuardar) => {
                console.log(`Se creo ${nuevo.name}`);
                res.status(204).json(personaGuardar);
            })
            .catch((err) => { next(err) })
       
    } else {
        res.status(400).send("No se pudo crear el contacto");
    }
});
 

Repetimos procesos anteriores.

Obtenemos lo datos. Se verifican que no estén vacíos.

Creamos la instancia con estos datos.

Lo nuevo: para guardar usamos el modelo nuevo y 'save', como es una acción asincronica, usamos( then y catch):

✅ Cuando esta bien (then): se guarda automáticamente (no es implícito en el código) y damos un mensaje para confirmar la acción.

❌ Cuando esta mal (catch): usamos next. ¿QUE ES ESO? lo veremos mas adelante.

 

4.3 PUT

Para actualizar todo haremos:

 
app.put("/api/persons/:id", (req, res, next) => {
    const id = req.params.id
    let datos = req.body

    const nuevaPersona = {
        name: datos.name,
        number: datos.number
    }

    Person.findByIdAndUpdate(id, nuevaPersona)
        .then(() => {
            console.log(`Se actualizo ${nuevaPersona.name}`);
            res.status(202).json(nuevaPersona);
        })
        .catch((err) => { next(err) })
})
 

Obtenemos el id y los datos.

Creamos un objeto con estos datos.

Usamos el modelo y 'findByIdAndUpdate' lo cual hace que por el id y un objeto actualice.

✅ Se actualiza, y se muestra un mensaje confirmando.

❌ Usamos next.

 

4.4 PATCH

Este es ligeramente mas complicado. Lo haremos así:

 
app.patch("/api/persons/:id", (req, res, next) => {
    const id = req.params.id;
    let datos = req.body;

    Person.findById(id)
        .then((persona) => {
            if (!persona) {
                return res.status(404).end("No se encontro esta persona");
            }

            const nuevaPersona = Object.fromEntries(
                Object.entries(datos).filter(([_, value]) => value !== undefined)
            );

            Object.assign(persona, nuevaPersona);

            return persona.save(); // Guardamos los cambios en la BD
        })
        .then((personaActualizada) => {
            console.log(`Se actualizó parcialmente`);
            res.status(202).json(personaActualizada);
        })
        .catch((err) => {
            next(err);
        });
});
 

Obtenemos los datos. Creamos un objeto con los datos.

Entonces con el modelo, buscamos primero si existe.

✅ Si existe, con el objeto 'nuevaPersona' obtenemos los datos que si se enviaron; luego actualizaremos el objeto con 'assing' y guardaremos.

✅ si pasa con éxito, daremos un mensaje confirmando la actualización.

❌ Si falla, usamos next.


 4.5 DELETE

Este es mas fácil:

 
app.delete("/api/persons/:id", (req, res, next) => {
    const id = req.params.id;

    if(id != null) {
        Person.findByIdAndDelete(id)
            .then(() => {
                console.log("Se elimino");
                res.status(204).end(`Se elimino ${id}`);
            })
            .catch((err) => { next(err) })
       
    } else {
        res.status(404).send("No existe esa persona");
    }
});
 

Obtenemos el id. Verificamos que exista.

Con 'findByIdAndDelete' borra automáticamente el objeto.

✅ Damos un mensaje de confirmación.

❌ Usamos next


5 ¿NEXT?

Que es esto que usamos ahora. Es un middleware.

Se usa así. En los procesos HTTP ahora necesitamos el parámetro next, y en el catch, los usamos como función y le enviamos el error.

Para crearlo, lo haremos en su carpeta correspondiente. 

El middleware sera así:

 
/* MIDDLEWARE
    Al haber un error en los procedimientos HTTP como estan en el GET y el PUT
    Maneja, segun el error, una cosa u otra
 */
module.exports = (err, req, res, next) => {
    console.log("ERROR:", err.name);
   
    if(err.name == "CastError") {
        res.status(404).end("Error en la busqueda")
    } else {
        res.status(500).end(`Error desconocido: ${err.name}`)
    }
}
 

 Se crea una función la cual hará algo según el tipo de error.

Esto es importante para manejar errores de una manera mas fácil.

En el código principal, al final para evitar errores de ejecución, pondremos:

 
app.use(handleErrors)
 

Esto se importo así:

 
const handleErrors = require("./middleware/handleErrors.js")