MySQL con Node.js: instalación y uso paso a paso
Lección 03 · Motores y ecosistema
MySQL con Node.js: instalación y uso paso a paso
SQLite es genial para empezar, pero muchas aplicaciones web necesitan un motor cliente-servidor: una base de datos que corre como un proceso aparte, acepta conexiones por red y da servicio a muchos usuarios a la vez. En esta lección instalamos MySQL y lo conectamos con Node.js paso a paso.
A diferencia de SQLite, aquí tu programa no abre un archivo: se conecta a un servidor MySQL (por defecto en el puerto 3306), le manda las consultas y recibe los resultados.
Diagrama: “Tu app (Node.js) —mysql2→ Servidor MySQL (:3306) —→ tienda (tablas)”. Tu código nunca toca los archivos: habla por red con un servidor que gestiona los datos.
Paso 1 · Instalar MySQL
Tienes varias formas de tener un MySQL funcionando. Elige la que más te convenga.
Opción A · Docker (la más rápida y recomendada)
Si tienes Docker, no necesitas instalar MySQL en tu sistema: lo levantas en un contenedor aislado. Es la forma más limpia y funciona igual en macOS, Linux y Windows.
docker run --name mysql-curso \
-e MYSQL_ROOT_PASSWORD=supersecreto \
-e MYSQL_DATABASE=tienda \
-p 3306:3306 \
-d mysql:8
Esto arranca MySQL 8, crea una base de datos llamada tienda y la expone en localhost:3306. Para pararlo o volver a arrancarlo:
docker stop mysql-curso
docker start mysql-curso
Opción B · Instalación nativa
Si prefieres instalarlo directamente en tu máquina:
# macOS (con Homebrew)
brew install mysql
brew services start mysql
# Ubuntu / Debian
sudo apt update && sudo apt install mysql-server
sudo systemctl start mysql
En Windows, descarga el MySQL Installer desde la web oficial y sigue el asistente. Tras instalar, es muy recomendable ejecutar mysql_secure_installation para poner una contraseña de root y endurecer la configuración.
Paso 2 · Crear la base de datos
Conéctate al servidor con el cliente de línea de comandos (si usaste Docker con MYSQL_DATABASE=tienda, este paso ya está hecho):
mysql -u root -p
Y crea la base de datos y, opcionalmente, un usuario específico para tu aplicación (mejor que usar root):
CREATE DATABASE IF NOT EXISTS tienda;
CREATE USER 'app'@'localhost' IDENTIFIED BY 'clave_de_app';
GRANT ALL PRIVILEGES ON tienda.* TO 'app'@'localhost';
FLUSH PRIVILEGES;
Paso 3 · Preparar el proyecto de Node
Crea una carpeta, inicia el proyecto e instala el driver oficial recomendado: mysql2. Es el paquete estándar para hablar con MySQL desde Node, con soporte de promesas y sentencias preparadas.
mkdir tienda-api && cd tienda-api
npm init -y
npm install mysql2
A diferencia de
node:sqlite, MySQL sí necesita una dependencia externa (mysql2), porque Node no trae un cliente de MySQL incorporado. Seguiremos usando TypeScript ejecutable directamente connode app.tsen Node 24.
Paso 4 · Conectar desde Node.js
Usamos siempre la variante con promesas: mysql2/promise. Así podemos usar async/await, como en cualquier otra operación asíncrona de Node.
import mysql from "mysql2/promise"
const connection = await mysql.createConnection({
host: "localhost",
port: 3306,
user: "app",
password: "clave_de_app",
database: "tienda",
})
console.log("✅ Conectado a MySQL")
Nunca escribas las credenciales en el código. En un proyecto real van en variables de entorno (
process.env.DB_PASSWORD), no escritas a mano como en estos ejemplos didácticos.
Paso 5 · Crear una tabla
Con la conexión abierta, ejecutamos SQL con connection.execute(). Fíjate en la sintaxis de MySQL que ya viste en el curso: AUTO_INCREMENT, VARCHAR(n) y DECIMAL.
await connection.execute(`
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
stock INT NOT NULL DEFAULT 0
)
`)
Paso 6 · Insertar datos (de forma segura)
Como en SQLite, usamos marcadores ? para pasar los valores. mysql2 los envía por separado y evita la inyección SQL. execute() devuelve un array; el primer elemento trae información del resultado.
const [result] = await connection.execute(
"INSERT INTO products (name, price, stock) VALUES (?, ?, ?)",
["Teclado mecánico", 79.99, 25]
)
console.log(result.insertId) // id autogenerado
console.log(result.affectedRows) // 1
Cuidado con la inyección SQL. Aquí es donde más importa: MySQL suele estar detrás de una aplicación web con datos que llegan de usuarios reales. Jamás construyas la consulta pegando esos datos con
+o template strings (`... ${dato} ...`); un atacante podría colar' OR '1'='1y saltarse tus filtros. Pasa siempre los valores como parámetros con?, tal y como hacemos aquí. Lo vimos en detalle en la lección de SQLite en Node.js.
Paso 7 · Consultar datos
Para un SELECT, el primer elemento del array son directamente las filas, como objetos de JavaScript:
const [rows] = await connection.execute(
"SELECT id, name, price FROM products WHERE price < ? ORDER BY price",
[100]
)
console.log(rows)
// [ { id: 1, name: 'Teclado mecánico', price: '79.99' } ]
En TypeScript conviene tipar las filas. mysql2 expone el tipo RowDataPacket, que puedes extender con tu propia forma:
import mysql from "mysql2/promise"
import type { RowDataPacket } from "mysql2"
interface Product extends RowDataPacket {
id: number
name: string
price: string
stock: number
}
const [products] = await connection.execute<Product[]>(
"SELECT * FROM products"
)
for (const product of products) {
console.log(`${product.name}: ${product.price} €`)
}
Paso 8 · Usar un pool de conexiones
Abrir una conexión por cada consulta es lento. En una aplicación real se usa un pool: un conjunto de conexiones reutilizables que mysql2 gestiona por ti. Es lo que querrás en cualquier servidor web.
import mysql from "mysql2/promise"
const pool = mysql.createPool({
host: "localhost",
user: "app",
password: "clave_de_app",
database: "tienda",
waitForConnections: true,
connectionLimit: 10, // hasta 10 conexiones simultáneas
})
// Usas el pool igual que una conexión, sin abrir ni cerrar nada
const [rows] = await pool.query("SELECT * FROM products")
El pool coge una conexión libre, ejecuta tu consulta y la devuelve al conjunto automáticamente.
execute vs query
mysql2 te ofrece dos métodos y conviene saber cuándo usar cada uno:
| Método | Qué hace | Cuándo usarlo |
|---|---|---|
execute() |
Usa sentencias preparadas. Manda SQL y valores por separado. | Siempre que pases datos con ?. Es la opción segura por defecto. |
query() |
Envía el SQL tal cual, sin preparar. | Consultas sin parámetros o SQL dinámico controlado por ti. |
En la práctica: usa execute() con ? para todo lo que involucre datos del usuario.
Una aplicación completa
Todo junto en un archivo tienda.ts, listo para node tienda.ts:
import mysql from "mysql2/promise"
const pool = mysql.createPool({
host: "localhost",
user: "app",
password: "clave_de_app",
database: "tienda",
connectionLimit: 10,
})
// Crear la tabla si no existe
await pool.execute(`
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
stock INT NOT NULL DEFAULT 0
)
`)
// Insertar un producto
await pool.execute(
"INSERT INTO products (name, price, stock) VALUES (?, ?, ?)",
["Ratón inalámbrico", 29.99, 40]
)
// Consultar los productos más baratos
const [rows] = await pool.execute(
"SELECT name, price FROM products WHERE price < ? ORDER BY price",
[50]
)
console.table(rows)
// Cerramos el pool para que el programa termine
await pool.end()
Fíjate en lo importante: el SQL es lo mismo que aprendiste en el curso. Cambia el motor y el driver, pero SELECT, WHERE, ORDER BY o JOIN son idénticos. Lo que has aprendido se transfiere directamente.
Practica el SQL de la tienda
Practica sobre una versión de la tabla products (adaptada al playground, que usa SQLite):
Nombre de la Tabla: products
| id | name | price | stock | category |
|---|---|---|---|---|
| 1 | Teclado mecánico | 79.99 | 25 | Periféricos |
| 2 | Ratón inalámbrico | 29.99 | 40 | Periféricos |
| 3 | Monitor 27” | 249.99 | 12 | Pantallas |
| 4 | Webcam HD | 59.99 | 0 | Periféricos |
| 5 | Silla ergonómica | 189.99 | 8 | Mobiliario |
| 6 | Alfombrilla XL | 14.99 | 100 | Periféricos |
En resumen
- MySQL es un motor cliente-servidor: tu app se conecta por red a un servidor en el puerto
3306. - La forma más cómoda de tener MySQL es con Docker; también puedes instalarlo de forma nativa.
- En Node se usa el driver
mysql2(npm install mysql2), siempre en su versión con promesas:mysql2/promise. - Usa
execute()con marcadores?para consultas con datos, y un pool de conexiones en aplicaciones reales. - El SQL que has aprendido en el curso funciona igual: solo cambian el motor y el driver.
Tablas de esta lección
Estructura
Datos de ejemplo (5 filas)
| id | name | price | stock | category |
|---|---|---|---|---|
| 1 | Teclado mecánico | 79.99 | 25 | Periféricos |
| 2 | Ratón inalámbrico | 29.99 | 40 | Periféricos |
| 3 | Monitor 27" | 249.99 | 12 | Pantallas |
| 4 | Webcam HD | 59.99 | 0 | Periféricos |
| 5 | Silla ergonómica | 189.99 | 8 | Mobiliario |
| 6 | Alfombrilla XL | 14.99 | 100 | Periféricos |
Ejercicios
Muestra el nombre y el precio de todos los productos que cuestan menos de 100, ordenados de más barato a más caro.
¿Qué productos están agotados (stock = 0)? Muestra solo su nombre.
Calcula cuántos productos hay en cada categoría y su precio medio. Muestra la categoría, el total de productos y el precio medio.
Calcula el valor total del inventario de cada producto (precio × stock). Muestra el nombre y el valor, ordenado de mayor a menor.