Integración de MySQL con aplicaciones Node.js

En esta guía, aprenda cómo integrar MySQL con aplicaciones Node.js, realizar operaciones CRUD, escapar de la entrada del usuario y establecer períodos de tiempo de espera con JavaScript.

Introducción

MySQL es un Sistema de administración de bases de datos relacionales (RDBMS) inmensamente popular, y durante mucho tiempo ha sido un elemento básico en el conjunto de herramientas de cualquier ingeniero de back-end, justo al lado de PostgreSQL.

Con la llegada de JavaScript para el back-end, a través del espectacular aumento de popularidad y aplicabilidad de Node.js, MySQL se usa comúnmente como base de datos para proyectos y aplicaciones creados con Node.

En esta guía, veremos cómo integrar MySQL con una aplicación Node.js. Pasaremos por el proceso de configuración, realizaremos operaciones CRUD mediante programación y echaremos un vistazo a la seguridad básica de las consultas escapando de la entrada y configurando los tiempos de espera.

{.icon aria-hidden=“true”}

Nota: A lo largo de la guía, nos referiremos al controlador MySQL npm como mysql y a la base de datos MySQL como MySQL.

Configuración de MySQL

Para trabajar con MySQL, deberá descargar la Base de datos MySQL y alojar un pequeño servidor en su máquina local. ¡La versión comunitaria es totalmente gratuita!

También puede optar por instalar un servidor web de código abierto compatible con la base de datos MySQL (Xampp, Lampp); también funcionan muy bien con el controlador MySQL de Node.

El instalador es sencillo, y una vez que haya configurado una instancia de servicio, ¡puede crear un proyecto para conectarse a él!

Controlador MySQL para proyectos de nodos

Suponiendo que el servidor se está ejecutando, puede comunicarse con él mediante programación a través de una aplicación Node, utilizando un controlador. Para que una aplicación tenga esta capacidad, debe instalar un Controlador MySQL. ¡Hay un controlador disponible en npm como mysql!

Para comenzar, creemos un nuevo directorio para alojar nuestra aplicación e inicialicemos un nuevo proyecto de Nodo, con la configuración predeterminada:

1
2
3
$ mkdir mysql-app
$ cd mysql-app
$ npm init -y

Esto generará un nuevo package.json que contiene los metadatos básicos para nuestro proyecto. Una vez hecho esto, podemos instalar el paquete del controlador MySQL del nodo a través de npm:

1
$ npm install mysql

Podemos verificar que la instalación fue exitosa revisando nuestro archivo package.json, donde deberíamos encontrar una nueva entrada para mysql agregada bajo dependencias:

1
2
3
4
 ...
 "dependencies": {
    "mysql": "^2.18.1"
  }

{.icon aria-hidden=“true”}

Nota: ^2.18.1 representa el número de versión, y esto variará según la versión de MySQL que haya instalado.

Estableciendo una conexión

Se puede crear un objeto de conexión a través de la función createConnection() de la instancia mysql. ¡Este objeto se puede usar para crear una conexión entre el servidor y el cliente/controlador! Acepta tres parámetros:

  • Host - El host de su base de datos. El valor predeterminado es 'localhost'.
  • Usuario: el nombre de usuario del administrador de la base de datos autorizado. El valor predeterminado es 'raíz'.
  • Contraseña - La contraseña del administrador de la base de datos autorizado. El valor predeterminado es una cadena vacía (sin contraseña).
  • Base de datos: un argumento opcional que define el nombre de la base de datos en la que desea realizar modificaciones.

Con esto en mente, abramos el archivo index.js predeterminado, y podemos conectarnos a un servidor MySQL con:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var mysql = require("mysql");

var connection = mysql.createConnection({
    host: "localhost",
    user: "root",
    password: "",
});

connection.connect((err) => {
    if (err) {
      console.log("Error occurred", err);
    } else {
      console.log("Connected to MySQL Server");
    }
});

Una vez que se ha configurado una conexión, ¡simplemente puede conectar () al servidor! La función connect() utiliza una devolución de llamada que le permite verificar si la conexión fue exitosa o no.

Escribir consultas a través de JavaScript {#escribir consultas a través de javascript}

Casi todas las acciones que necesita realizar en una base de datos MySQL se realizan a través de consultas, escritas, naturalmente, en SQL. El método query() de la instancia connection acepta una cadena, que representa una consulta que nos gustaría enviar a la base de datos.

Una consulta puede fallar o devolver algún resultado, por lo que tendremos otra devolución de llamada con un err o un resultado según cómo haya ido la operación.

Crear una base de datos

Comencemos creando una base de datos a través del controlador MySQL de Node. Puede crear una base de datos en el servidor a través de la CLI (si la ha instalado) o a través de cualquier forma de GUI, ya sea oficial o de terceros.

Alternativamente, puede usar la conexión que hemos creado para enviar una consulta que crea una base de datos para usted. Vamos a crear una nueva base de datos students_records. En el directorio raíz del proyecto, en el mismo index.js que hemos usado antes, agreguemos una llamada query() después de conectarnos a la base de datos, si la conexión fue exitosa:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var mysql = require("mysql");

// Create connection configuration
var connection = mysql.createConnection({
    host: "localhost",
    user: "root",
    password: "",
});

// Connect to the server
connection.connect((err) => {
    if (err) {
      // Return error if present
      console.log("Error occurred", err);
    } else {
      // Create database
      console.log("Connected to MySQL Server");
      const query = "CREATE DATABASE students_records";
      connection.query(query, function (err, result) {
        if (err) {
          err;
        }
        console.log("New database created");
      });
    }
});

Después de verificar para ver si nuestra conexión fue exitosa, ejecutamos una consulta con una cadena de consulta: 'CREATE DATABASE Students_records', que crea una nueva base de datos si aún no está presente. Ahora, ejecutemos el proyecto:

1
$ node index.js

Lo que resulta en:

1
2
Connected to MySQL Server
New database created

¡Impresionante! Ahora que existe una base de datos students_records, podemos eliminar la consulta de creación de la base de datos y conectarnos directamente a students_records en la configuración:

1
2
3
4
5
6
var connection = mysql.createConnection({
    host: "localhost",
    user: "root",
    password: "",
    database: "students_records",
});

Creación de registros

Al igual que cualquier otra base de datos relacional, los registros de datos en MySQL se almacenan en tablas con columnas y filas. Una tabla puede constar de cualquier número arbitrario de columnas y filas, pero tienen que ser coherentes. Las columnas de la base de datos representan características/campos de un objeto y cada fila representa una única entrada.

Cada fila debe tener un valor para cada columna y solo para esas columnas, incluso si es simplemente NULL. El esquema debe permanecer consistente para cualquier base de datos relacional.

Esto significa que necesitaremos crear una tabla por adelantado y definir su esquema (columnas y tipos para cada uno) para agregar nuevos registros a nuestra base de datos.

Para crear una nueva tabla en MySQL, usamos la instrucción CREATE TABLE. Digamos que nos gustaría crear una tabla students en nuestra base de datos students_records. Nuevamente, una vez conectado, podemos simplemente ejecutar la consulta relevante:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var mysql = require("mysql");

var connection = mysql.createConnection({
    host: "localhost",
    user: "root",
    password: "",
    database: "students_records",
});

connection.connect((err) => {
    if (err) {
      console.log("Error occurred", err);
    } else {
      console.log("Connected to database");
      var sql = "CREATE TABLE students (id INT, name VARCHAR(255), course VARCHAR(255))";
      connection.query(sql, function (err, result) {
        if (err) {
          console.log(err);
        }
        console.log("New table created");
      });
    }
});

¡Esto crea una nueva tabla que puede contener registros con los campos nombre y curso! Cada entrada tendrá un nombre y un curso, incluso si algunos de ellos son nulos. Además, hemos configurado su tipo para que sea VARCHAR con una longitud máxima de 255 caracteres.

Aquí es donde también puede establecer otras restricciones, como que ciertos campos sean obligatorios (NOT NULL), o CLAVE PRINCIPAL/CLAVE EXTERNA:

1
2
3
4
5
6
CREATE TABLE students (
  id INT NOT NULL,
  name VARCHAR(255) NOT NULL,
  course VARCHAR(255) NOT NULL,
  PRIMARY KEY (ID)
)

Aquí, ninguno de los campos se puede establecer en NULL, y cada uno de ellos tiene algún tipo de datos asociado.

{.icon aria-hidden=“true”}

Nota: En SQL, no importa si usa mayúsculas o no en las palabras clave. create table es funcionalmente equivalente a CREATE TABLE, como varchar lo es a VARCHAR. Es una convención común usar mayúsculas en los comandos, palabras clave y restricciones, aunque no es un requisito.

Crear registro

¡La consulta INSERT INTO, seguida del nombre de la tabla y los VALUES asignados a sus columnas se usa para insertar/crear registros con SQL! Por ejemplo, agreguemos un John Doe a nuestra tabla students:

1
var sql = "INSERT INTO students (id, name, course) VALUES (1, 'John Doe', 'Computer Science')";

Esta declaración, como era de esperar, insertaría un registro (fila) en la tabla estudiantes, con valores para el nombre y el curso. La consulta, como de costumbre, se puede ejecutar a través de connection.query():

1
2
3
4
5
6
7
8
var sql = "INSERT INTO students (id, name, course) VALUES (1, 'John Doe', 'Computer Science')";

// Just execute
connection.query(sql);
// Or execute with callback to handle results/errors
connection.query(sql, function (err, result) {
  // ...
});

Lectura de registros

No solo queremos almacenar datos, también queremos poder leerlos. Y, como se ve a continuación, podemos recuperar datos de la tabla students con la consulta SELECT.

Podemos seleccionar registros específicos o usar un comodín (*) para seleccionar todos los disponibles:

1
2
3
4
var sql = "SELECT * FROM students";
connection.query(sql, function (err, result) {
  console.log(result);
});

Ejecutar este código devolverá todos los datos presentes en nuestra tabla:

1
2
3
4
[
  RowDataPacket { id: 1, name: 'John Doe', course: 'Computer Science' },
  RowDataPacket { id: 2, name: 'Jane Doe', course: 'Art' }
]

Además, puede usar la instrucción BETWEEN para crear un rango de filas coincidentes:

1
SELECT * FROM students WHERE id BETWEEN 1 AND 5

Lo que devolvería a todos los alumnos, cuyo ID esté entre 1 y 5.

Actualización de registros

Podemos usar la instrucción UPDATE para actualizar las tablas de la base de datos y su contenido. Por ejemplo, puede usar varias declaraciones para identificar un determinado registro y actualizar ese registro. En términos generales, un ‘id’ debe ser una ‘CLAVE PRINCIPAL’ para la identificación de una fila, así como un valor único, por lo que el ‘id’ suele ser la columna por la que buscar registros.

Digamos que queremos actualizar el curso del estudiante, ‘John Doe’, de ‘Ciencias de la computación’ a ‘Arte’, junto con su primo. El id de John es 1:

1
2
3
4
5
var sql = "UPDATE students SET course = 'Art' WHERE id = 1";

connection.query(sql, function (err, result) {
  console.log(result);
});

El resultado de una consulta como esta generalmente incluye detalles sobre cómo la consulta afecta la tabla, que es cómo puede verificar si el resultado funcionó como esperaba o no:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 {
  fieldCount: 0,
  affectedRows: 1,
  insertId: 1,
  serverStatus: 34,
  warningCount: 0,
  message: '(Rows matched: 1  Changed: 1  Warnings: 0',
  protocol41: true,
  changedRows: 1
}

Eliminación de registros

Finalmente, para eliminar registros, usamos la declaración DELETE y especificamos qué registros se eliminarán:

1
2
3
4
var sql = "DELETE FROM students WHERE id = 1";
connection.query(sql, function (err, result) {
  console.log(result);
});

{.icon aria-hidden=“true”}

Nota: Debe tener cuidado al usar la declaración DELETE y especificar la cláusula WHERE con cuidado. Además, si no especifica la cláusula WHERE, se borrará todo el contenido de la tabla.

Escapar valores de consulta

Se descubrió un exploit temprano común cuando los ingenieros de software permitieron que la entrada del usuario se usara dentro de las llamadas SQL. Por ejemplo, podría tener una barra de búsqueda con un campo de entrada. El usuario insertaría el nombre de un libro y esa entrada se usaría dentro de una consulta como:

1
SELECT * FROM books WHERE name = user_input

La entrada_usuario sería, naturalmente, reemplazada por una cadena, como 'Fundación'. Sin embargo, esto abre una puerta para vulnerabilidades enormes, que son tan simples como escapar de la consulta que ha creado y ejecutar la suya propia.

Por ejemplo, si el sistema se creó para aceptar ingenuamente la entrada e insertarla en la consulta, bastaría con que el usuario ingresara a; DROP TABLE books para explotar su consulta:

1
SELECT * FROM books WHERE name = a; DROP TABLE books

El punto y coma terminaría la declaración anterior y se ejecutaría la siguiente en línea, eliminando toda la tabla de la base de datos y eliminando todos los libros. Este es un ejemplo simple de Inyección SQL, y los ataques pueden volverse mucho más complejos que esto. Además, no todos los ataques pueden ser destructivos: un atacante también puede simplemente leer datos confidenciales, que es un ataque silencioso que generalmente pasa desapercibido.

Dada la prevalencia de estos ataques, la mayoría de los paquetes tienen seguridad básica incorporada. El controlador mysql creado para Node proporciona funciones y medidas para ayudar a evitar las inyecciones de SQL.

El método más común es valores de escape y usarlos como parámetros con nombre en su lugar:

1
2
3
4
5
const input = 'Foundation';

connection.query('SELECT * FROM books WHERE name = ?', [input], function (err, results, fields) {
// ... 
});

Al usar el carácter de escape ?: escapa de la entrada y la convierte en una cadena literal, que no se puede evaluar. Si alguien ingresó un comando en el campo de entrada que conduce a este parámetro, no se evaluaría como un comando. Se trataría como una cadena literal y se enviaría una consulta para encontrar un libro cuyo nombre coincida con ese comando.

El método acepta una matriz de parámetros, que se ingresan secuencialmente en la consulta. Si la consulta tiene 2 parámetros anotados, el primer elemento de la lista se asignará al primer parámetro y los segundos elementos de la lista se asignarán al segundo parámetro.

Alternativamente, puede inyectar estos valores explícitamente a través del formato de cadena de JavaScript y usar el método escape() para escapar de los posibles comandos, aunque este enfoque es generalmente menos legible:

1
2
3
4
5
const input = mysql.escape('Foundation');

connection.query(`SELECT * FROM books WHERE name = ${input}`, function (err, results, fields) {
// ... 
});

Configuración de tiempos de espera

Cada operación relacionada con la conexión finalizará después de un período de tiempo de espera predeterminado, y podemos solucionar esto configurando nuestra propia sesión de tiempo de espera preferida. Esto se hace pasando un objeto a la función .query(), con nuestra consulta y el tiempo de espera de la sesión (en milisegundos) como los campos de ese objeto:

1
2
3
connection.query({sql: 'SELECT * FROM students', timeout: 50000}, function (err, results) {
// ...
});

Este código ejecutará una consulta SELECT que finalizará después de 50 segundos, si no se devuelve ningún resultado.

Conclusión

MySQL es una base de datos relacional de uso común. En esta guía, hemos analizado cómo instalar controladores para un servidor MySQL para un proyecto Node.js, cómo configurar una conexión con el servidor y realizar operaciones CRUD.

Finalmente, hemos notado la importancia de escapar de la entrada del usuario y echamos un vistazo a la personalización de los períodos de tiempo de espera. a.