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.
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.
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.
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
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.
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
.
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
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
.
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"); %>
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>
<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
.
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>
<a href="/productos" class="btn btn-primary">Volver</a>
</div>
</form>
</div>
<%- include("../footer"); %>
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"); %>
Ahora basta con ejecutar la app usando:
set debug=crud-mysql:* & npm start
Y visitando:
localhost:3000/productos
Recuerda que el código completo puedes verlo en mi GitHub así como la estructura del mismo.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.
Ver comentarios
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
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 }
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
Consulta Puedo realizar algo parecido pero sin tener que usar los archivos .ejs Puedo hacerlo desde el html directamente?
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