Aprende SQL

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.

Tu app
Node.js
mysql2
:3306
Servidor MySQL
:3306
SQL
tienda
tablas

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 necesita una dependencia externa (mysql2), porque Node no trae un cliente de MySQL incorporado. Seguiremos usando TypeScript ejecutable directamente con node app.ts en 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'='1 y 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

products5 cols

Estructura

🔑idINTEGERnameTEXTpriceREALstockINTEGERcategoryTEXT

Datos de ejemplo (5 filas)

idnamepricestockcategory
1Teclado mecánico79.9925Periféricos
2Ratón inalámbrico29.9940Periféricos
3Monitor 27"249.9912Pantallas
4Webcam HD59.990Periféricos
5Silla ergonómica189.998Mobiliario
6Alfombrilla XL14.99100Perifé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.

sql

¿Qué productos están agotados (stock = 0)? Muestra solo su nombre.

sql

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.

sql

Calcula el valor total del inventario de cada producto (precio × stock). Muestra el nombre y el valor, ordenado de mayor a menor.

sql