Conexión Node.js y MySQL con Express

En este post te voy a mostrar cómo conectarte a una base de datos de MySQL usando el lenguaje de programación JavaScript  del lado del servidor a través del entorno Node; también conocido como Node.Js.

Vamos a hacer las 4 operaciones básicas de la base de datos: obtener datos, insertar datos, actualizar datos y eliminar datos.

Para la interfaz del usuario vamos a usar Bootstrap y una estructura de aplicación web generada con express-generator.

Instalar ejemplo de código

El ejemplo de todo este CRUD de MySQL con Node que vamos a crear está en mi GitHub. Las instrucciones igualmente están en el repositorio.

A través del post explicaré las partes más importantes, pero el código completo y ejecutable está en el repositorio.

Recuerda instalar MySQL en Windows, Linux o Android y aprender un poco de la CLI o resolver algunos ejercicios.

Obviamente necesitas tener el entorno de Node. Aquí tengo unos tutoriales para instalar Node en Windows y en Android.

Tabla

Vamos a usar una tabla simple de productos, para evitar complejidades, relaciones y esas cosas:

create table productos(
    id integer not null auto_increment,
    nombre varchar(255),
    precio decimal(5, 2),
    primary key(id)
);

La tabla tiene únicamente un id autoincrementable, el nombre del producto y un precio de tipo decimal.

Librería

Aunque al instalar los paquetes usando npm install vas a tener la librería, no está de más indicar que la misma se instala con:

npm install --save mysql

Archivo de conexión

Vamos a crear un archivo de conexión que vamos a incluir en donde necesitemos al a misma.

En este archivo ponemos las credenciales de acceso. Si no tienes base de datos o usuario recuerda crearlos.

const mysql = require("mysql");
// Coloca aquí tus credenciales
module.exports = mysql.createPool({
  host: "localhost",
  user: "root",
  password: "",
  database: "tienda"
});

Invocamos al método createPool para que la conexión se abra y cierre de manera automática.

El modelo y la base de datos

Intenté abstraer esto en un esquema de Modelo vista controlador o MVC.

Lo único que hace el modelo de productos es tener las 4 operaciones básicas usando promesas:

const conexion = require("../conexion")
module.exports = {
    insertar(nombre, precio) {
        return new Promise((resolve, reject) => {
            conexion.query(`insert into productos
            (nombre, precio)
            values
            (?, ?)`,
                [nombre, precio], (err, resultados) => {
                    if (err) reject(err);
                    else resolve(resultados.insertId);
                });
        });
    },
    obtener() {
        return new Promise((resolve, reject) => {
            conexion.query(`select id, nombre, precio from productos`,
                (err, resultados) => {
                    if (err) reject(err);
                    else resolve(resultados);
                });
        });
    },
    obtenerPorId(id) {
        return new Promise((resolve, reject) => {
            conexion.query(`select id, nombre, precio from productos where id = ?`,
                [id],
                (err, resultados) => {
                    console.log({resultados});
                    if (err) reject(err);
                    else resolve(resultados[0]);
                });
        });
    },
    actualizar(id, nombre, precio) {
        return new Promise((resolve, reject) => {
            conexion.query(`update productos
            set nombre = ?,
            precio = ?
            where id = ?`,
                [nombre, precio, id],
                (err) => {
                    if (err) reject(err);
                    else resolve();
                });
        });
    },
    eliminar(id) {
        return new Promise((resolve, reject) => {
            conexion.query(`delete from productos
            where id = ?`,
                [id],
                (err) => {
                    if (err) reject(err);
                    else resolve();
                });
        });
    },
}

Estamos invocando al método query, el mismo funciona tanto para hacer consultas como para hacer inserciones, actualizaciones y eliminaciones.

Dentro de los ejemplos tenemos dos maneras de llamar a query con la siguiente sintaxis:

db.query("consulta", parámetros, callback)

Y cuando no hay datos que mandar dentro de la consulta (indicados con ?)

db.query("consulta", callback)

En el callback resolvemos o rechazamos la promesa.

Por cierto, para obtener el último id insertado (no siempre es necesario pero igual lo pongo) podemos acceder a través de resultados.insertId.

Inyecciones SQL

Estamos previniendo inyecciones SQL, ya que no estamos concatenando, sino indicando los parámetros con signos de interrogación.

En lugar de algo como:

"insert into tabla(valor) values("+ valor +")"

Hacemos algo así:

insert into tabla(valor) values (?) y pasamos los valores en un arreglo, en el mismo orden.

Por cierto, si notas las cadenas verás que no tienen comillas dobles ni simples, sino backticks. Lo hago para soportar cadenas multilinea

Las rutas y express

Los métodos del modelo son llamados desde en enrutador de Express:

const express = require('express');
const router = express.Router();

const productosModel = require("../models/productos");

router.get('/', function (req, res, next) {
    productosModel
        .obtener()
        .then(productos => {
            res.render("productos/ver", {
                productos: productos,
            });
        })
        .catch(err => {
            return res.status(500).send("Error obteniendo productos");
        });

});
router.get('/agregar', function (req, res, next) {
    res.render("productos/agregar");
});
router.post('/insertar', function (req, res, next) {
    // Obtener el nombre y precio. Es lo mismo que
    // const nombre = req.body.nombre;
    // const precio = req.body.precio;
    const { nombre, precio } = req.body;
    if (!nombre || !precio) {
        return res.status(500).send("No hay nombre o precio");
    }
    // Si todo va bien, seguimos
    productosModel
        .insertar(nombre, precio)
        .then(idProductoInsertado => {
            res.redirect("/productos");
        })
        .catch(err => {
            return res.status(500).send("Error insertando producto");
        });
});
router.get('/eliminar/:id', function (req, res, next) {
    productosModel
        .eliminar(req.params.id)
        .then(() => {
            res.redirect("/productos");
        })
        .catch(err => {
            return res.status(500).send("Error eliminando");
        });
});
router.get('/editar/:id', function (req, res, next) {
    productosModel
        .obtenerPorId(req.params.id)
        .then(producto => {
            if (producto) {
                console.log({ producto });
                res.render("productos/editar", {
                    producto: producto,
                });
            } else {
                return res.status(500).send("No existe producto con ese id");
            }
        })
        .catch(err => {
            return res.status(500).send("Error obteniendo producto");
        });
});
router.post('/actualizar/', function (req, res, next) {
    // Obtener el nombre y precio. Es lo mismo que
    // const nombre = req.body.nombre;
    // const precio = req.body.precio;
    const { id, nombre, precio } = req.body;
    if (!nombre || !precio || !id) {
        return res.status(500).send("No hay suficientes datos");
    }
    // Si todo va bien, seguimos
    productosModel
        .actualizar(id, nombre, precio)
        .then(() => {
            res.redirect("/productos");
        })
        .catch(err => {
            return res.status(500).send("Error actualizando producto");
        });
});

module.exports = router;

Las rutas mostradas son relativas a /productos. Es decir, en donde dice simplemente / es como si dijera /productos.

El método res.render regresa una vista, el mismo toma el nombre de la vista y opcionalmente datos que se le pasan a las vistas.

Algunas rutas son para procesar formularios o eliminaciones a través de una variable de la URL, en ese caso simplemente se realiza la operación y luego se redirige con res.redirect.

Vistas con EJS

Hasta el momento ya tenemos la forma de interactuar con nuestro modelo, a través de las rutas. Solo falta programar los formularios y tablas.

Comenzamos definiendo un encabezado y un pie que serán comunes a través de todas las páginas; como lo dije, estoy usando Bootstrap así que el encabezado es el siguiente:

<!doctype html>
<html lang="es">
<!--
  Plantilla inicial de Bootstrap 4
  @author parzibyte
  Visita: parzibyte.me/blog
-->

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="Conexión MySQL y Node">
    <meta name="author" content="Parzibyte">
    <title>Conexión MySQL y Node</title>
    <!-- Cargar el CSS de Boostrap-->
    <link href="/stylesheets/bootstrap.min.css" rel="stylesheet">
    <!-- Cargar estilos propios -->
    <link href="/stylesheets/style.css" rel="stylesheet">
</head>

<body>
    <!-- Definición del menú -->
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
        <a class="navbar-brand" target="_blank" href="//parzibyte.me/blog">Node y MySQL - By Parzibyte</a>
        <button aria-label="Mostrar u ocultar menú" class="navbar-toggler" id="botonMenu" type="button">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="menu">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <a class="nav-link" href="/productos">Ver</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/productos/agregar">Agregar</a>
                </li>
            </ul>
        </div>
    </nav>
    <script type="text/javascript">
        // Tomado de https://github.com/parzibyte/cotizaciones_web/blob/master/js/cotizaciones.js#L2
        document.addEventListener("DOMContentLoaded", () => {
            const menu = document.querySelector("#menu"),
                botonMenu = document.querySelector("#botonMenu");
            if (menu) {
                botonMenu.addEventListener("click", () => menu.classList.toggle("show"));
            }
        });
    </script>
    <!-- Termina la definición del menú -->
    <main role="main" class="container">
        <div class="row">

Y el pie el siguiente:

</div>
</main>
</body>

</html>

Con eso ya tenemos unas templates. Ahora falta diseñar lo que irá dentro de las mismas.

Nota: para incluir el encabezado y el pie en otras vistas, usamos <%- include("archivo"); %>

Formulario para insertar (insert)

El formulario para insertar datos queda así:

<%- include("../header"); %>
<div class="col-12">
    <h1>Agregar producto</h1>
    <form method="post" action="/productos/insertar">
        <div class="form-group">
            <label for="nombre">Nombre</label>
            <input required id="nombre" placeholder="Nombre del producto" class="form-control" type="text"
                name="nombre">
        </div>
        <div class="form-group">
            <label for="precio">Precio</label>
            <input required id="precio" placeholder="Precio del producto" class="form-control" type="number"
                name="precio">
        </div>
        <div class="form-group">
            <button class="btn btn-success">Guardar</button>
            &nbsp;<a href="/productos" class="btn btn-primary">Volver</a>
        </div>
    </form>
</div>
<%- include("../footer"); %>

Su action es a /productos/insertar como lo pusimos en las rutas. Para procesar el formulario en el enrutador, accedemos al valor de name.

Formulario de Bootstrap para enviar producto

Formulario para editar (update)

El de editar simplemente recibe un id a través de una variable de ruta, después lo usa para traer un dato de la base de datos (a través del modelo) y luego llena los datos del formulario estableciendo los valores con el atributo value.

<%- include("../header"); %>
<div class="col-12">
    <h1>Editar producto</h1>
    <form method="post" action="/productos/actualizar">

        <input value="<%= producto.id %>" name="id" type="hidden">
        <div class="form-group">
            <label for="nombre">Nombre</label>
            <input value="<%= producto.nombre %>" required id="nombre" placeholder="Nombre del producto"
                class="form-control" type="text" name="nombre">
        </div>
        <div class="form-group">
            <label for="precio">Precio</label>
            <input value="<%= producto.precio %>" required id="precio" placeholder="Precio del producto"
                class="form-control" type="number" name="precio">
        </div>
        <div class="form-group">
            <button class="btn btn-success">Guardar</button>
            &nbsp;<a href="/productos" class="btn btn-primary">Volver</a>
        </div>
    </form>
</div>
<%- include("../footer"); %>
Formulario de edición de producto

Listar valores en tabla (select)

Ahora veamos la vista que dibuja los productos de nuestra base de datos. Hacemos un simple ciclo foreach y dibujamos la tabla.

Dentro de la tabla colocamos dos enlaces que servirán para editar y eliminar los productos de MySQL:

<%- include("../header"); %>
<div class="col-12">
    <h1>Productos</h1>
    <a href="/productos/agregar" class="btn btn-primary mb-2">Agregar</a>
    <table class="table table-bordered">
        <thead>
            <tr>
                <th>Nombre</th>
                <th>Precio</th>
                <th>Editar</th>
                <th>Eliminar</th>
            </tr>
        </thead>
        <tbody>
            <% productos.forEach(producto => { %>
            <tr>
                <td><%= producto.nombre %></td>
                <td><%= producto.precio %></td>
                <td>
                    <a href="/productos/editar/<%= producto.id %>" class="btn btn-warning">
                        Editar
                    </a>
                </td>
                <td>
                    <a href="/productos/eliminar/<%= producto.id %>" class="btn btn-danger">
                        Eliminar
                    </a>
                </td>
            </tr>
            <% })%>
        </tbody>
    </table>
</div>
<%- include("../footer"); %>

Poniendo todo junto

Ahora basta con ejecutar la app usando:

set debug=crud-mysql:* & npm start

Y visitando:

localhost:3000/productos

Captura de app de NodeJS

Recuerda que el código completo puedes verlo en mi GitHub así como la estructura del mismo.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

5 comentarios en “Conexión Node.js y MySQL con Express”

  1. Quisiera hacer una consulta yo estoy comenzando a incursionar en el Mundo del uso de NodeJS. Estoy Utilizando: Express, Handlebars y MySQL.

    La consulta que quisiera hacer es referente al como podría cargar datos en un select desde Mysql para que el usuario pueda ver los datos de ese select (se trata de una lista de profesiones); al registrarse el usuario debe seleccionar su profesión (y los datos de ese select no son estáticos sino que vienen desde MySQL). Yo programo normalmente en PHP y uso AJAX y me traigo el contenido desde el modelo a la vista sin problema; pero ahora que estoy NodeJS no tengo idea de como hacer lo mismo. Y estoy algo varado. Si alguien me puede orientar en como puedo hacer esto se lo agradezco mucho

  2. hola muchas gracias por muy buen POST. He creado un usuario para mi database le he dado acceso para hacer CRUD en mi database y desde antes de este tengo el siguiente error en el momento de hacer cualquiera de las operaciones CRUD. Agradezco la ayuda de antemano. Un saludo

    { Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
    at Handshake.Sequence._packetToError (C:\Users\home\Desktop\expressPrueba\node_modules\mysql\lib\protocol\sequences\Sequence.js:47:14)
    at Handshake.ErrorPacket (C:\Users\home\Desktop\expressPrueba\node_modules\mysql\lib\protocol\sequences\Handshake.js:123:18)
    at Protocol._parsePacket (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\protocol\Protocol.js:291:23)
    at Parser._parsePacket (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\protocol\Parser.js:433:10)
    at Parser.write (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\protocol\Parser.js:43:10)
    at Protocol.write (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\protocol\Protocol.js:38:16)
    at Socket. (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\Connection.js:91:28)
    at Socket. (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\Connection.js:525:10)
    at Socket.emit (events.js:198:13)
    at addChunk (_stream_readable.js:288:12)
    ——————–
    at Protocol._enqueue (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\protocol\Protocol.js:144:48)
    at Protocol.handshake (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\protocol\Protocol.js:51:23)
    at PoolConnection.connect (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\Connection.js:119:18)
    at Pool.getConnection (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\Pool.js:48:16)
    at Pool.query (C:\Users\home\Desktop\expressApp\node_modules\mysql\lib\Pool.js:202:8)
    at Promise (C:\Users\home\Desktop\expressApp\database\dbOperations.js:8:26)
    at new Promise ()
    at Object.insertUser (C:\Users\Robert\Desktop\expressPrueba\database\dbOperations.js:7:24)
    at router.post (C:\Users\Robert\Desktop\expressPrueba\routes\singup.js:20:34)
    at Layer.handle [as handle_request] (C:\Users\Robert\Desktop\expressPrueba\node_modules\express\lib\router\layer.js:95:5)
    code: ‘ER_NOT_SUPPORTED_AUTH_MODE’,
    errno: 1251,
    sqlMessage:
    ‘Client does not support authentication protocol requested by server; consider upgrading MySQL client’,
    sqlState: ‘08004’,
    fatal: true }

    1. Hola, es porque el paquete de Node todavía no soporta esa nueva versión. Puede quitar un poco de seguridad (si usted está haciendo pruebas) ejecutando en la consola de MySQL o en PHPMyAdmin lo siguiente:
      ALTER USER 'usuario'@'localhost' IDENTIFIED WITH mysql_native_password BY 'contraseña'; FLUSH PRIVILEGES;
      Simplemente cambie el usuario y contraseña de acuerdo a las credenciales que usted tenga.
      También puede revisar esto: https://stackoverflow.com/questions/50093144/mysql-8-0-client-does-not-support-authentication-protocol-requested-by-server
      Saludos

    1. Hola, claro, puedes definir las vistas en HTML sin EJS (incluyendo el encabezado manualmente por ejemplo), pero sí lo necesitarás para renderizar la tabla, por ejemplo.
      De igual manera podrías en su lugar crear una API y renderizar del lado del cliente, al final queda en ti todo lo que hagas
      Saludos

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *