Back to Blog

MySQL for Beginners: Complete Guide (2026)

Learn MySQL from zero — installation to building a real database with tables, queries, JOINs, and best practices step-by-step.

MySQL for Beginners: Complete Guide (2026)

The only MySQL beginner guide you need — from installation to writing powerful queries with hands-on examples

1. What is MySQL? SQL vs MySQL

MySQL is an open-source Relational Database Management System (RDBMS) that stores data in tables (rows and columns) and lets you query it using SQL (Structured Query Language).

Created in 1995 and now owned by Oracle, MySQL powers WordPress, Facebook, Uber, Airbnb, Netflix and millions of websites worldwide. It is the #1 most popular open-source database.

SQL vs MySQL — The Common Confusion

Term What It Is Analogy
SQL Language to talk to databases English language
MySQL Software that understands SQL Person who speaks English
PostgreSQL, SQLite Other software that also understand SQL Other people who also speak English
-- SQL is the same across databases (90% similar):
SELECT * FROM users WHERE age > 18;

-- MySQL is the database that EXECUTES this SQL and returns results.
-- PostgreSQL, SQLite, SQL Server all run similar SQL.

How Data is Stored: Tables

Table: users
+----+----------+-------+---------------------+
| id | name     | age   | email               |
+----+----------+-------+---------------------+
| 1  | Avinash  | 28    | avi@example.com     |
| 2  | Priya    | 26    | priya@example.com   |
| 3  | Rohan    | 30    | rohan@example.com   |
+----+----------+-------+---------------------+
  ↑      ↑        ↑       ↑
 PK   VARCHAR   INT     VARCHAR  — columns have types
 Rows = records, Columns = fields

In simple words: Think of MySQL as Excel on steroids — tables are like sheets, rows are records, columns are fields. But unlike Excel, MySQL handles millions of rows, enforces rules (constraints), links tables together (JOINs), and serves data to your apps via queries.

2. Why Use MySQL?

Advantages

  • Free & open-source — Community Edition costs nothing, enterprise support available
  • Beginner friendly — simple installation, readable SQL, huge tutorial community
  • Proven at scale — handles millions of queries per second at Facebook / Uber
  • Works everywhere — Node.js, PHP, Python, Java, WordPress, shared hosting
  • ACID compliant — Atomicity, Consistency, Isolation, Durability — your data stays safe
  • Great tooling — MySQL Workbench, phpMyAdmin, CLI, and cloud options (PlanetScale, AWS RDS)

When NOT to Use MySQL

  • Unstructured data (JSON-heavy docs) → consider MongoDB
  • Need advanced geospatial or full-text search → PostgreSQL may be better
  • Tiny local app with no server → SQLite is simpler (single file, zero config)
✅ Use MySQL for: blogs, e-commerce, SaaS, user accounts, inventories
✅ Use MySQL when: data has clear relationships (users → orders → products)
✅ Use MySQL when: you need transactions (payments must not lose money)

3. How MySQL Works: Architecture

Understanding the big picture before writing queries makes everything click faster.

// How your app talks to MySQL:
// ┌──────────┐    SQL Query     ┌──────────────────┐    Reads/Writes   ┌────────┐
// │ Your App │ ──────────────→  │   MySQL Server   │ ───────────────→ │ Disk   │
// │ (Node.js)│ ←──────────────  │  (mysqld process)│ ←─────────────── │ Files  │
// └──────────┘   Result Rows    └──────────────────┘                  └────────┘
//
// Inside MySQL Server:
// 1. Connection Layer  — handles client connections & auth
// 2. SQL Layer         — parses SQL, optimizes, executes
// 3. Storage Engine    — InnoDB (default) stores actual data on disk

Key Concepts

  • Server (mysqld) — the MySQL program running in background that holds all databases
  • Client (mysql) — CLI or GUI that connects to the server to run queries
  • Database — a container for tables (like a folder). One server can have many databases
  • Table — rows + columns inside a database
  • Storage Engine — InnoDB is the default (supports transactions, foreign keys, row-level locking)

Mental model: MySQL Server is a building. Inside it are databases (apartments). Inside each apartment are tables (rooms). Inside each room are rows (furniture). SQL is the language you use to ask the building manager to fetch or rearrange things.

4. Installation & Setup

Get MySQL running in under 5 minutes — pick one method.

Option 1: Official Installer (Easiest for Beginners)

# 1. Download MySQL Community Server from https://dev.mysql.com/downloads/mysql/
# 2. Choose your OS (Windows / macOS / Linux)
# 3. Install — set a root password when prompted (remember it!)
# 4. Verify in terminal:

mysql --version
# mysql  Ver 8.0.42 for Win64

mysql -u root -p
# Enter password: ****
# mysql>   ← you are now inside MySQL shell
# Type EXIT; to leave

Option 2: Docker (Recommended for Developers)

# Run MySQL 8 in one command — no system install needed
docker run --name mysql-dev \
  -e MYSQL_ROOT_PASSWORD=mypassword \
  -e MYSQL_DATABASE=testdb \
  -p 3306:3306 -d mysql:8

# Connect
docker exec -it mysql-dev mysql -u root -p
# or
mysql -h 127.0.0.1 -u root -p

# Stop / start later
docker stop mysql-dev
docker start mysql-dev

Option 3: Online — No Install

Practice instantly without installing: db-fiddle.com, SQL Fiddle, or OneCompiler MySQL.

Install MySQL Workbench (GUI)

Download from dev.mysql.com/downloads/workbench — visual table designer, query editor, and server dashboard. Great for beginners who prefer clicking over typing.

Common mistake: Forgetting the root password. If you do, reset it via mysqld --skip-grant-tables or reinstall via Docker (just delete the container and rerun). For learning, use a simple password like root or password — change it for production.

5. Your First Database & Table

Let's create a database and a table — the foundation of every MySQL project.

-- ============================================
-- Connect and explore
-- ============================================
SHOW DATABASES;   -- list all databases

-- Create your own database
CREATE DATABASE shop_db;
USE shop_db;     -- switch to it
SELECT DATABASE(); -- confirms: shop_db

-- ============================================
-- Create a table
-- ============================================
CREATE TABLE users (
  id       INT AUTO_INCREMENT PRIMARY KEY,
  name     VARCHAR(100) NOT NULL,
  email    VARCHAR(150) NOT NULL UNIQUE,
  age      INT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Check it
SHOW TABLES;
DESCRIBE users;   -- show columns and types
SHOW CREATE TABLE users; -- full definition

Insert & View Data

-- Insert rows
INSERT INTO users (name, email, age) VALUES
  ('Avinash', 'avi@example.com', 28),
  ('Priya',   'priya@example.com', 26),
  ('Rohan',   'rohan@example.com', 30);

-- View
SELECT * FROM users;

-- Result:
-- +----+---------+---------+------+---------------------+
-- | id | name    | email   | age  | created_at          |
-- +----+---------+---------+------+---------------------+
-- |  1 | Avinash | avi@... |   28 | 2026-09-23 10:00:00 |
-- +----+---------+---------+------+---------------------+

Semicolons matter. Every SQL statement ends with ;. In the MySQL CLI you can write multi-line queries — it waits for ; before executing. Press Enter without ; and you get a -> continuation prompt — just add ; and press Enter again.

6. Data Types Explained

Choosing the right type saves space, prevents bugs, and speeds up queries.

Category Type Use For Example
Numeric INT Whole numbers age INT → 28
DECIMAL(10,2) Money (exact) price DECIMAL(10,2) → 1999.99
String VARCHAR(n) Variable-length text name VARCHAR(100)
CHAR(n) Fixed length (e.g. country code) code CHAR(2) → 'IN'
TEXT Long text (articles, comments) body TEXT
Date/Time DATE Date only 2026-09-23
DATETIME Date + time 2026-09-23 10:00:00
TIMESTAMP Auto time zone aware DEFAULT CURRENT_TIMESTAMP
Other BOOLEAN True/false (stored as TINYINT) is_active BOOLEAN
ENUM('a','b') Pick from list status ENUM('pending','shipped')
JSON JSON data (MySQL 5.7+) meta JSON
-- Example: products table with varied types
CREATE TABLE products (
  id          INT AUTO_INCREMENT PRIMARY KEY,
  name        VARCHAR(200) NOT NULL,
  description TEXT,
  price       DECIMAL(10,2) NOT NULL,
  stock       INT DEFAULT 0,
  is_active   BOOLEAN DEFAULT TRUE,
  created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Money tip: Never use FLOAT or DOUBLE for money — they cause rounding errors (0.1 + 0.2 ≠ 0.3). Always use DECIMAL(10,2) for prices, balances, and financial values.

7. CRUD — Create, Read, Update, Delete

CRUD is 90% of what you do with a database. Master these four operations.

-- ============================================
-- C — CREATE (INSERT)
-- ============================================
-- Single row
INSERT INTO users (name, email, age) VALUES ('Neha', 'neha@example.com', 24);

-- Multiple rows at once
INSERT INTO users (name, email, age) VALUES
  ('Aman', 'aman@example.com', 29),
  ('Sara', 'sara@example.com', 31);

-- Insert and get the new ID
INSERT INTO users (name, email) VALUES ('Test', 'test@example.com');
SELECT LAST_INSERT_ID(); -- returns the auto-increment id

-- ============================================
-- R — READ (SELECT)
-- ============================================
SELECT * FROM users;                          -- all rows, all columns
SELECT name, email FROM users;                -- only specific columns
SELECT * FROM users WHERE age > 25;           -- filtered
SELECT * FROM users WHERE id = 2;             -- single row by PK
SELECT COUNT(*) FROM users;                   -- count rows

-- ============================================
-- U — UPDATE
-- ============================================
-- ⚠️ Always use WHERE — without it, you update EVERY row!
UPDATE users SET age = 29 WHERE id = 1;
UPDATE users SET name = 'Avinash B.', email = 'avi.b@example.com' WHERE id = 1;

-- ============================================
-- D — DELETE
-- ============================================
-- ⚠️ Always use WHERE — without it, you delete EVERY row!
DELETE FROM users WHERE id = 5;

-- Delete with condition
DELETE FROM users WHERE age < 18;

-- Remove all rows but keep table structure
TRUNCATE TABLE users;  -- faster than DELETE without WHERE

-- Delete the entire table
DROP TABLE users;      -- ⚠️ irreversible!

Danger zone: Running UPDATE users SET age = 0; or DELETE FROM users; without WHERE affects every row. Before any UPDATE/DELETE, first run a SELECT with the same WHERE to preview which rows will be affected.

8. Filtering: WHERE, LIKE, IN, BETWEEN

WHERE is how you ask precise questions of your data.

-- ============================================
-- Comparison operators
-- ============================================
SELECT * FROM users WHERE age = 28;        -- equal
SELECT * FROM users WHERE age != 28;       -- not equal
SELECT * FROM users WHERE age > 25;        -- greater than
SELECT * FROM users WHERE age >= 25;       -- greater or equal
SELECT * FROM users WHERE age < 30;        -- less than

-- ============================================
-- Logical operators
-- ============================================
SELECT * FROM users WHERE age > 25 AND city = 'Hyderabad';
SELECT * FROM users WHERE age < 20 OR age > 60;
SELECT * FROM users WHERE NOT age = 28;

-- ============================================
-- IN — match any in a list
-- ============================================
SELECT * FROM users WHERE city IN ('Hyderabad', 'Mumbai', 'Delhi');
SELECT * FROM users WHERE id IN (1, 3, 5);

-- ============================================
-- BETWEEN — range inclusive
-- ============================================
SELECT * FROM users WHERE age BETWEEN 25 AND 35;
SELECT * FROM products WHERE price BETWEEN 100 AND 500;

-- ============================================
-- LIKE — pattern matching
-- ============================================
SELECT * FROM users WHERE name LIKE 'A%';     -- starts with A
SELECT * FROM users WHERE name LIKE '%n';     -- ends with n
SELECT * FROM users WHERE email LIKE '%@gmail.com'; -- Gmail users
SELECT * FROM users WHERE name LIKE '%vi%';   -- contains "vi"

-- Case-insensitive by default in MySQL (collation)
-- Use BINARY for case-sensitive:
SELECT * FROM users WHERE BINARY name = 'avinash'; -- exact case

-- ============================================
-- NULL handling
-- ============================================
SELECT * FROM users WHERE phone IS NULL;      -- missing phone
SELECT * FROM users WHERE phone IS NOT NULL;  -- has phone
-- ⚠️ Never use = NULL — always IS NULL / IS NOT NULL

9. Sorting & Limiting: ORDER BY, LIMIT

-- Sort ascending (default A→Z, 0→9, old→new)
SELECT * FROM users ORDER BY name ASC;
SELECT * FROM users ORDER BY age ASC;

-- Sort descending (Z→A, 9→0, new→old)
SELECT * FROM users ORDER BY age DESC;
SELECT * FROM products ORDER BY price DESC;

-- Multiple sort keys
SELECT * FROM users ORDER BY city ASC, age DESC;
-- sort by city first, then by age within each city

-- Limit results
SELECT * FROM users LIMIT 5;               -- first 5 rows
SELECT * FROM users LIMIT 10 OFFSET 20;    -- skip 20, take 10 (page 3 if page size 10)

-- Pagination: page 1 → LIMIT 10 OFFSET 0
--            page 2 → LIMIT 10 OFFSET 10
--            page 3 → LIMIT 10 OFFSET 20

-- Combine: get 5 most expensive products
SELECT name, price FROM products ORDER BY price DESC LIMIT 5;

-- Newest users first
SELECT * FROM users ORDER BY created_at DESC LIMIT 10;

OFFSET is slow for large tables. For deep pagination (page 1000+), use keyset pagination instead: SELECT * FROM users WHERE id > 1000 ORDER BY id LIMIT 10 — this uses the index and stays fast even at millions of rows.

10. Constraints: PRIMARY KEY, FOREIGN KEY, UNIQUE

Constraints are rules that keep your data clean and consistent — they prevent bugs at the database level.

CREATE TABLE users (
  id    INT AUTO_INCREMENT PRIMARY KEY,  -- unique, never null, auto number
  email VARCHAR(150) NOT NULL UNIQUE,    -- no duplicates allowed
  name  VARCHAR(100) NOT NULL,           -- must have a value
  age   INT CHECK (age >= 0 AND age <= 120), -- valid range
  city  VARCHAR(100) DEFAULT 'Unknown'   -- default if not provided
);

CREATE TABLE orders (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  user_id    INT NOT NULL,
  amount     DECIMAL(10,2) NOT NULL CHECK (amount > 0),
  status     ENUM('pending','shipped','delivered') DEFAULT 'pending',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  -- Foreign key: orders.user_id must exist in users.id
  FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE CASCADE    -- if user deleted, delete their orders
    ON UPDATE CASCADE
);

-- What each constraint does:
-- PRIMARY KEY  → unique + not null + indexed (one per table, usually `id`)
-- UNIQUE       → no duplicates (e.g. email)
-- NOT NULL     → column must have a value
-- CHECK        → value must satisfy condition
-- FOREIGN KEY  → value must exist in another table
-- DEFAULT      → value if none given

Foreign Key Actions

Action When parent is deleted Use when
CASCADE Delete child rows too Orders belong to user — delete together
SET NULL Set child FK to NULL Optional relationship
RESTRICT Block deletion if children exist Prevent accidental loss (default)

11. JOINs: INNER, LEFT, RIGHT

JOINs combine rows from two or more tables based on a related column — the most powerful SQL concept.

-- Setup: two related tables
-- users:  id, name, email
-- orders: id, user_id, product, amount

-- Sample data:
-- users:  (1, Avinash), (2, Priya), (3, Rohan)
-- orders: (1, user_id=1, Laptop, 60000)
--         (2, user_id=1, Mouse, 800)
--         (3, user_id=2, Keyboard, 3000)
-- Rohan (id=3) has NO orders

INNER JOIN — Only Matching Rows

SELECT users.name, orders.product, orders.amount
FROM users
INNER JOIN orders ON users.id = orders.user_id;

-- Result (only users who HAVE orders):
-- Avinash | Laptop   | 60000
-- Avinash | Mouse    | 800
-- Priya   | Keyboard | 3000
-- Rohan is EXCLUDED (no orders)

LEFT JOIN — All Left Table Rows + Matching Right

SELECT users.name, orders.product, orders.amount
FROM users
LEFT JOIN orders ON users.id = orders.user_id;

-- Result (ALL users, even without orders):
-- Avinash | Laptop   | 60000
-- Avinash | Mouse    | 800
-- Priya   | Keyboard | 3000
-- Rohan   | NULL     | NULL   ← included with NULLs

-- Find users who never ordered:
SELECT users.name
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE orders.id IS NULL;  -- Rohan

RIGHT JOIN — All Right Table Rows

SELECT users.name, orders.product
FROM users
RIGHT JOIN orders ON users.id = orders.user_id;
-- All orders, even if user was deleted (rare — LEFT JOIN is preferred)
-- Visual cheat sheet:
-- INNER JOIN:   A ∩ B           (intersection)
-- LEFT JOIN:    A + (A ∩ B)     (all of A, matches from B)
-- RIGHT JOIN:   B + (A ∩ B)     (all of B, matches from A)

-- Multiple JOINs:
SELECT users.name, orders.product, products.category
FROM users
JOIN orders   ON users.id = orders.user_id
JOIN products ON orders.product_id = products.id
WHERE users.city = 'Hyderabad';

Which JOIN to use? Use INNER JOIN when you want only rows with matches in both tables. Use LEFT JOIN when you want all rows from the left table even if there is no match — e.g., "show all users and their orders, if any." 95% of the time you need one of these two.

12. Aggregations: COUNT, SUM, AVG, GROUP BY

Aggregate functions summarize data — total sales, average age, orders per user.

-- Count rows
SELECT COUNT(*) FROM users;                    -- total users
SELECT COUNT(*) FROM orders WHERE status = 'shipped';

-- Sum, Avg, Min, Max
SELECT SUM(amount) FROM orders;                -- total revenue
SELECT AVG(amount) FROM orders;                -- average order value
SELECT MIN(price) FROM products;              -- cheapest product
SELECT MAX(price) FROM products;              -- most expensive

-- GROUP BY — aggregate per group
-- Orders per user:
SELECT user_id, COUNT(*) AS order_count, SUM(amount) AS total_spent
FROM orders
GROUP BY user_id;

-- Result:
-- user_id | order_count | total_spent
--       1 |           2 |       60800
--       2 |           1 |        3000

-- Revenue per status:
SELECT status, COUNT(*) AS cnt, SUM(amount) AS revenue
FROM orders
GROUP BY status;

-- HAVING — filter AFTER grouping (WHERE filters BEFORE)
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id
HAVING total > 5000;   -- only users who spent > 5000

-- ⚠️ WHERE vs HAVING:
-- WHERE  → filters rows before grouping
-- HAVING → filters groups after grouping

Complete Example: Top Customers

SELECT
  users.name,
  COUNT(orders.id) AS orders_count,
  SUM(orders.amount) AS total_spent
FROM users
LEFT JOIN orders ON users.id = orders.user_id
GROUP BY users.id, users.name
ORDER BY total_spent DESC
LIMIT 5;

13. Indexes & Performance

An index is like a book's table of contents — it helps MySQL find rows without scanning every page.

-- Without index: MySQL scans ALL rows (slow for large tables)
SELECT * FROM users WHERE email = 'avi@example.com';
-- → Full table scan: checks every row

-- Add an index:
CREATE INDEX idx_email ON users(email);
-- Now the same query uses the index → instant even at 1M rows

-- Check if index is used:
EXPLAIN SELECT * FROM users WHERE email = 'avi@example.com';
-- Look for: type = ref or const, key = idx_email (good)
-- vs type = ALL (bad — full scan)

-- Auto-indexed columns (no need to create manually):
-- PRIMARY KEY is always indexed
-- UNIQUE columns are indexed
-- FOREIGN KEY columns are indexed

-- Manual indexes — add for columns you filter/sort/join often:
CREATE INDEX idx_city ON users(city);
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_created ON orders(created_at);

-- Composite index (multiple columns — order matters!)
CREATE INDEX idx_city_age ON users(city, age);
-- Fast for: WHERE city = 'Hyd' AND age > 25
-- Fast for: WHERE city = 'Hyd'
-- NOT fast for: WHERE age > 25 (city is first in index)

-- Remove index:
DROP INDEX idx_city ON users;

Don't over-index. Each index speeds up reads but slows down writes (INSERT/UPDATE) and uses disk space. Index only columns you actually filter, join, or sort by. Start with no extra indexes, add them when EXPLAIN shows a full table scan on a slow query.

14. Project: E-Commerce Database from Scratch

Let's build a complete mini shop database — users, products, orders — with relationships and real queries.

-- ============================================
-- Step 1: Create database
-- ============================================
DROP DATABASE IF EXISTS shop_db;
CREATE DATABASE shop_db;
USE shop_db;

-- ============================================
-- Step 2: Create tables with relationships
-- ============================================
CREATE TABLE users (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  name       VARCHAR(100) NOT NULL,
  email      VARCHAR(150) NOT NULL UNIQUE,
  city       VARCHAR(100),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE products (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  name       VARCHAR(200) NOT NULL,
  category   VARCHAR(100),
  price      DECIMAL(10,2) NOT NULL CHECK (price >= 0),
  stock      INT DEFAULT 0,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE orders (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  user_id    INT NOT NULL,
  product_id INT NOT NULL,
  quantity   INT NOT NULL CHECK (quantity > 0),
  amount     DECIMAL(10,2) NOT NULL,
  status     ENUM('pending','shipped','delivered','cancelled') DEFAULT 'pending',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
  FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE RESTRICT
);
-- ============================================
-- Step 3: Insert sample data
-- ============================================
INSERT INTO users (name, email, city) VALUES
  ('Avinash', 'avi@example.com', 'Hyderabad'),
  ('Priya',   'priya@example.com', 'Mumbai'),
  ('Rohan',   'rohan@example.com', 'Hyderabad');

INSERT INTO products (name, category, price, stock) VALUES
  ('Laptop',     'Electronics', 60000.00, 10),
  ('Mouse',      'Electronics',   800.00, 100),
  ('Keyboard',   'Electronics',  3000.00, 50),
  ('Desk Chair', 'Furniture',   12000.00, 20);

INSERT INTO orders (user_id, product_id, quantity, amount, status) VALUES
  (1, 1, 1, 60000.00, 'delivered'),
  (1, 2, 2,  1600.00, 'shipped'),
  (2, 3, 1,  3000.00, 'pending'),
  (2, 4, 1, 12000.00, 'delivered');
-- ============================================
-- Step 4: Real queries — try each!
-- ============================================

-- 1. All orders with user name and product name
SELECT users.name, products.name AS product, orders.quantity, orders.amount, orders.status
FROM orders
JOIN users    ON orders.user_id = users.id
JOIN products ON orders.product_id = products.id;

-- 2. Total revenue
SELECT SUM(amount) AS total_revenue FROM orders WHERE status != 'cancelled';

-- 3. Revenue per city
SELECT users.city, SUM(orders.amount) AS revenue
FROM orders JOIN users ON orders.user_id = users.id
GROUP BY users.city;

-- 4. Top selling product
SELECT products.name, SUM(orders.quantity) AS total_sold
FROM orders JOIN products ON orders.product_id = products.id
GROUP BY products.id
ORDER BY total_sold DESC LIMIT 1;

-- 5. Users who never ordered
SELECT name FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE orders.id IS NULL;

-- 6. Low stock alert
SELECT name, stock FROM products WHERE stock < 20;

-- 7. Monthly sales
SELECT DATE_FORMAT(created_at, '%Y-%m') AS month, SUM(amount) AS sales
FROM orders
GROUP BY month
ORDER BY month;

15. Connect MySQL with Node.js

Most beginners want to use MySQL from JavaScript. Here is the minimal modern setup.

# Install driver
npm install mysql2 dotenv

# .env
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=mypassword
DB_NAME=shop_db
// ============================================
// db.js — connection pool (ESM)
// ============================================
import 'dotenv/config';
import mysql from 'mysql2/promise';

const pool = mysql.createPool({
  host:     process.env.DB_HOST,
  user:     process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  waitForConnections: true,
  connectionLimit: 10,
});

export default pool;

// ============================================
// Usage — query with parameterized SQL (prevents SQL injection!)
// ============================================
import pool from './db.js';

// SELECT
const [users] = await pool.query('SELECT * FROM users WHERE city = ?', ['Hyderabad']);
console.log(users);

// INSERT (safe from injection)
const [result] = await pool.query(
  'INSERT INTO users (name, email, city) VALUES (?, ?, ?)',
  ['Neha', 'neha@example.com', 'Delhi']
);
console.log('Inserted ID:', result.insertId);

// UPDATE
await pool.query('UPDATE users SET city = ? WHERE id = ?', ['Pune', 1]);

// JOIN
const [orders] = await pool.query(`
  SELECT users.name, products.name AS product, orders.amount
  FROM orders
  JOIN users ON orders.user_id = users.id
  JOIN products ON orders.product_id = products.id
`);

// Express route example
import express from 'express';
const app = express();
app.use(express.json());

app.get('/api/users', async (req, res) => {
  try {
    const [rows] = await pool.query('SELECT * FROM users');
    res.json(rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000, () => console.log('🚀 API at http://localhost:3000'));

Never concatenate user input into SQL. `SELECT * FROM users WHERE name = '${req.body.name}'` is vulnerable to SQL injection — an attacker can delete your database. Always use ? placeholders with parameter arrays as shown above.

16. Best Practices & Cheatsheet

Essential SQL Cheatsheet

-- Databases
SHOW DATABASES;  CREATE DATABASE db;  USE db;  DROP DATABASE db;

-- Tables
SHOW TABLES;  DESCRIBE tbl;  DROP TABLE tbl;  TRUNCATE TABLE tbl;

-- CRUD
INSERT INTO tbl (col) VALUES ('val');
SELECT col FROM tbl WHERE cond ORDER BY col LIMIT n;
UPDATE tbl SET col='val' WHERE cond;
DELETE FROM tbl WHERE cond;

-- Filtering
WHERE col = val  |  IN (a,b)  |  BETWEEN x AND y  |  LIKE '%pat%'  |  IS NULL

-- JOINs
SELECT * FROM A INNER JOIN B ON A.id = B.a_id;
SELECT * FROM A LEFT JOIN B ON A.id = B.a_id WHERE B.id IS NULL; -- A without B

-- Aggregation
COUNT(*)  SUM(col)  AVG(col)  MIN(col)  MAX(col)  GROUP BY col  HAVING cond

-- Indexes
CREATE INDEX idx ON tbl(col);  DROP INDEX idx ON tbl;  EXPLAIN SELECT ...;

Golden Rules

  • Always use PRIMARY KEY — every table needs an INT AUTO_INCREMENT PRIMARY KEY
  • Use FOREIGN KEY — let the database enforce relationships, not just your app code
  • Use DECIMAL for money — never FLOAT
  • Parameterize queries — use ? placeholders to prevent SQL injection
  • Index wisely — primary keys are auto-indexed; add indexes for WHERE/JOIN columns
  • Backup regularly — mysqldump -u root -p shop_db > backup.sql
  • Name consistently — tables plural lowercase (users), columns snake_case (created_at)
  • Use NOT NULL + DEFAULT — avoid unexpected NULLs

Backup & restore: mysqldump -u root -p shop_db > backup.sql to export, and mysql -u root -p shop_db < backup.sql to restore. Run this before any major change or migration.

17. MySQL vs PostgreSQL vs SQLite

Feature MySQL PostgreSQL SQLite
Best for Web apps, WordPress, SaaS Complex queries, analytics Local apps, mobile, prototyping
Setup Server install / Docker Server install / Docker Single file, zero config
Scale Millions of rows, replication Millions, advanced features Small to medium apps
JSON support JSON column (basic) JSONB (powerful, indexed) JSON1 extension
Transactions Yes (InnoDB) Yes (full ACID) Yes
Learning curve Low — beginner friendly Medium Very low
Hosting Everywhere (cheap) Render, Supabase, AWS RDS Embedded only
When to pick Default for most web projects Need advanced SQL features No server / offline first

18. Frequently Asked Questions

Is MySQL the same as SQL?

No. SQL is the language (SELECT * FROM users). MySQL is the database software that understands and executes SQL. Other databases like PostgreSQL and SQLite also use SQL — learning MySQL teaches you SQL you can use anywhere.

Do I need to install MySQL to learn it?

For serious practice, yes — install MySQL Community Server or run it via Docker (docker run -e MYSQL_ROOT_PASSWORD=pass -p 3306:3306 -d mysql:8). For quick experiments, use online tools like db-fiddle.com or OneCompiler without any install.

MySQL vs PostgreSQL — which should a beginner learn?

Start with MySQL. It is simpler to set up, powers more beginner-friendly stacks (WordPress, PHP, Node), and 90% of what you learn transfers to PostgreSQL. Move to PostgreSQL later if you need JSONB, full-text search, or advanced analytics.

Is MySQL still relevant in 2026?

Absolutely. MySQL is the most popular open-source relational database, used by millions of sites and top companies. Knowing it is a standard backend interview requirement and a must-have skill for full-stack developers.

What is the difference between CHAR and VARCHAR?

CHAR(10) always uses 10 characters (pads with spaces) — good for fixed-length codes like CHAR(2) for country codes. VARCHAR(100) uses only as much space as needed — good for names, emails. Use VARCHAR for most text.

How long does it take to learn MySQL?

Basic CRUD and filtering: 2–3 days. JOINs and aggregations: 1–2 weeks with practice. Production-ready skills (indexes, constraints, Node.js integration): 1 month of building real projects like the e-commerce database above.

Your next steps: 1) Build the shop database above → 2) Add authentication tables (users + sessions) → 3) Connect it to an Express API → 4) Learn transactions (START TRANSACTION; COMMIT; ROLLBACK;) for payment flows → 5) Deploy with PlanetScale or AWS RDS. Each step levels up your backend skills.

MySQL is the backbone of data-driven applications. Start with databases and tables, master CRUD, then unlock the real power with JOINs and aggregations. Practice with the e-commerce project, connect it to Node.js, and you will have a portfolio-ready full-stack skill. Open your terminal, run mysql -u root -p, and write your first CREATE TABLE today.

Back to Top