[Aporte] Como usar MySQL en NodeJs (Codigo de ejemplo)

Iniciado por [u]nsigned, 15 Mayo 2020, 03:56 AM

0 Miembros y 1 Visitante están viendo este tema.

[u]nsigned

Hola, queria compartirles este Ejemplo basico de como implementar MySQL en Node. Usando Promesas y async/await para dominar la asincronia sin bloquear el Event Loop.

Archivo mysql.js contiene la conexión a MySQL.
Código (javascript) [Seleccionar]

const mysql = require("mysql");

const mysqlConnection = mysql.createConnection({
  host: "localhost",
  port: 3306,
  user: "root",
  password: "",
  database: "",
});

mysqlConnection.connect((err) => {
  if (err) {
    console.error("[DB] No se pudo conectar con MYSQL", err);
  } else {
    console.log("[DB] Conectado con MYSQL", mysqlConnection.threadId);
  }
});

module.exports = mysqlConnection;


Archivo con la clase principal. Implementa las 5 funciones básicas de CRUD+L = Create/Crear, Read/Leer, Update/Modificar, Delete/Borrar, List/Listar

Código (javascript) [Seleccionar]

const MySQL = require("./mysql");

function list(table) {
  return new Promise(async (resolve, reject) => {
    const sql = `SELECT * FROM ${table}`;

    await MySQL.query(sql, (err, results) => {
      if (err) {
        console.error("[DB]", err);
        return reject({ message: err, code: 401 });
      }
      return resolve(results);
    });
  });
}

function get(table, id) {
  return new Promise(async (resolve, reject) => {
    const sql = `SELECT * FROM ${table} WHERE id=${id}`;

    await MySQL.query(sql, (err, results) => {
      if (err) {
        console.error("[DB]", err);
        return reject({ message: err, code: 401 });
      }
      return resolve(results);
    });
  });
}

function insert(table, data) {
  return new Promise(async (resolve, reject) => {
    try {
      const fields = Object.keys(data);
      const values = Object.values(data);

      if (fields.length === 0) {
        return reject({ message: "Faltan datos", code: 400 });
      }

      const sql = `INSERT INTO ${table} (${fields}) VALUES (${values.map(
        (h) => `'${h}'`
      )})`;

      await MySQL.query(sql, async (err, results) => {
        if (err) return reject({ message: err.message, code: 400 });

        const insertedData = await get(table, results.insertId);

        return resolve(insertedData);
      });
    } catch (error) {
      return reject(error);
    }
  });
}

function update(table, id, data) {
  return new Promise(async (resolve, reject) => {
    try {
      const fields = Object.keys(data);
      const values = Object.values(data);

      if (fields.length === 0) {
        return reject({ message: "Faltan datos", code: 400 });
      }

      const sql = `UPDATE ${table} SET ? WHERE id=${id}`;

      const beforeUpdate = await get(table, id);

      await MySQL.query(sql, data, async (err, results) => {
        if (err) return reject({ message: err.message, code: 400 });

        const updateData = await get(table, id);

        return resolve({ ...results, beforeUpdate, updateData });
      });
    } catch (error) {
      return reject(error);
    }
  });
}

function remove(table, id) {
  return new Promise(async (resolve, reject) => {
    try {
      const beforeUpdate = await get(table, id);

      if (beforeUpdate.length === 0)
        return reject({ message: `El elemento ${id} no existe`, code: 403 });

      const sql = `DELETE FROM ${table} WHERE id=${id} LIMIT 1`;

      await MySQL.query(sql, async (err, results) => {
        if (err) return reject({ message: err.message, code: 400 });

        return resolve({ ...results, beforeUpdate });
      });
    } catch (error) {
      return reject(error);
    }
  });
}

module.exports = {
  list,
  get,
  insert,
  update,
  remove,
};



Esta clase no tiene ningún tipo de validación, eso se debería hacer en otra capa, como por ejemplo en el controlador con @hapi/joi.

Para usarse simplemente debe importarse este modulo desde otro e invocar a la función CRUDL, por ejemplo

Ejemplo de Uso
Código (javascript) [Seleccionar]

const db = require("./db.js"); // suponiendo que el archivo anterior se llame "db.js y
                                           // ademas este ubicado en el mismo directorio que el presente codigo.

// Usar como funcion asincrona que debuelve un resultado o un error
// Operacion CRUDL: Listar la tabla 'usuarios' completa
// SIEMPRE dentro de una funcion async

async function getUsers(){
    const myUsers = await db.get('usuarios')
    return await db.get('usuarios')
    // return await db.get('usuarios')
}

console.log(getUsers())

// O usar como funciones que devuelven promesas, y gestionarla con then/cath:
// Operacion CRUDL: (D) Borrar el registro de la tabla 'usuarios' cuyo id sea igual a '666'

db.remove('usuarios', 666)//Borrar usuario con id 666
.then(users=>{
    // Se borro el usuario, hacer algo
    console.log(`Se borro el user 666`)
})
.catch(error=>{
   console.log(error)
})


Por ultimo, como tambien hago frontend, una cosa muy util es poder deshacer cualquier accion con un solo click, por cuestiones de UX/UI es una forma muy poderosa de fidelizar al usuario.

Por eso en las consultas 'remove' y 'update' se devuelve un objeto beforeUpdate y updateData

Código (javascript) [Seleccionar]

"beforeUpdate": [
            {
                "id": 14,
                "nombre": "Departamento de Coordinacion y Gestion Cooperativa",
                "responsable": ""
            }
        ]


Eso es muy util para revertir la ejecucion de la queri con un simple boton o 'call to action' como tiene gmail.



No hay atajo ante la duda, el misterio se hace aquí...
Se hace carne en cada uno, el misterio es existir!

MinusFour

El paquete de mysql en node.js no usa promesas hasta donde yo tengo entendido. Por eso existe promise-mysql (que es un wrapper sobre mysql, aunque quizás sea mejor ver el paquete de mysql2 en su lugar (porque ya adopta promesas). De esa forma ya no tienes que preocuparte por promisificar los metodos.

await trabaja con promesas. Puedes usar la instrucción para envolver valores en promesas pero en tu código, await nunca espera a que la callback de mysql.query sea llamada. Si funciona con tus get porque si regresa promesas (que se resuelven de manera asincrona).

[u]nsigned

Es cierto, normalmente se usa mysql2, de hecho es lo que usan todos los orm como Sequelize.

Pero era a modo de ejemplo, para los que recién empiezan en node. Ademas yo me acostumbre a trabajar así en express, encerrando todo en promesas, que es la forma correcta para luego usar middlewares a diferentes niveles (auth, error, routing, etc)

Citarawait trabaja con promesas. Puedes usar la instrucción para envolver valores en promesas pero en tu código, await nunca espera a que la callback de mysql.query sea llamada. Si funciona con tus get porque si regresa promesas (que se resuelven de manera asincrona).

No entendi del todo, pero las callbacks que mysql.query los uso asi por lo que te comentaba antes, para poder manejarlos mejor a nivel de middleware.

Quizás omiti el pequeño (o fundamental ) detalle de que este código esta pensado exclusivamente para usarse con el framework express.

No hay atajo ante la duda, el misterio se hace aquí...
Se hace carne en cada uno, el misterio es existir!

MinusFour

[quote author=nsigned link=topic=504750.msg2221958#msg2221958 date=1589876162]
No entendi del todo, pero las callbacks que mysql.query los uso asi por lo que te comentaba antes, para poder manejarlos mejor a nivel de middleware.

Quizás omiti el pequeño (o fundamental ) detalle de que este código esta pensado exclusivamente para usarse con el framework express.
[/quote]

Código (javascript) [Seleccionar]

(async function(){
  await setTimeout(() => {
    console.log('mensaje en callback');
  }, 2000);
  console.log('pasando a await');
})();


Imprime 'pasando a await' primero y luego 'mensaje en callback'.

Código (javascript) [Seleccionar]

(async function(){
  await (new Promise(ful => setTimeout(ful, 2000))).then(() => {
    console.log('mensaje en callback');
  }, 2000);
  console.log('pasando a await');
})();


Imprime 'mensaje en callback' primero y luego 'pasando a await'.

Lo que tu hiciste con mysql.query fue lo primero porque esa funcion no regresa una promesa.

Pudiste haber hecho:

Código (javascript) [Seleccionar]
let query = sql => new Promise((ful, rej) => {
  mysql.query(sql, (err, res) => {
    if(err) rej(err);
    ful(res);
  });
});


Y despsués simplemente:

Código (javascript) [Seleccionar]

query(sql).then(res => {
   /* resultados aqui */
});


Y para esta función si puedes usar await (correctamente).

Código (javascript) [Seleccionar]

(async function(){
  let resultados = await query(sql);
})();


Casi no es necesario trabajar con new Promise. Es preferible usar las librerias que promisifican las APIs de manera especifica o de manera general. Node por ejemplo tiene util.promisify

Lo que significa que pudiste haber hecho algo como:

Código (javascript) [Seleccionar]

let query = util.promisify(mysql.query.bind(mysql));


Y asi no tienes que trabajar con new Promise.

[u]nsigned


No hay atajo ante la duda, el misterio se hace aquí...
Se hace carne en cada uno, el misterio es existir!