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.
Node.js :3306 tablas 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.
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:8Esto 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-cursoOpció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 mysqlEn 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 -pY 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 mysql2A 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) // 1Cuidado 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):
Ejercicios
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.