Skip to main content
Express.js Tutorial
CHAPTER 12 Beginner

Connecting Express.js with MySQL

Updated: May 14, 2026
30 min read

# CHAPTER 12

Connecting Express.js with MySQL

1. Introduction

While MongoDB is extremely popular in the Node.js ecosystem, much of the world's enterprise data still lives in Relational Databases (SQL). If you are building financial software, inventory management systems, or applications where data is heavily interconnected (e.g., Users have many Orders, Orders have many Products), a relational database is superior. In this chapter, we will learn how to connect Express to MySQL, execute SQL queries using Promises, and return structured relational data.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Differentiate between NoSQL (MongoDB) and SQL (MySQL) paradigms.
  • Install and configure the mysql2 package.
  • Create a reusable MySQL connection pool.
  • Execute raw SQL queries asynchronously using async/await.

3. Beginner-Friendly Explanation

Imagine organizing a massive corporate office.
  • MongoDB is a series of file folders. You can put a resume, a photo, and a contract all inside "Alice's Folder." It is flexible, but hard to generate complex reports.
  • MySQL is a series of massive Excel Spreadsheets. Data must fit perfectly into rows and columns. "Alice" goes in the User sheet. Her "Purchases" go in the Order sheet. The sheets are linked together by ID numbers. It is strict, rigid, but incredibly powerful for math, accounting, and complex relationships.

4. Step 1: Installing the Driver

Historically, the mysql package in Node.js relied heavily on "callbacks," which resulted in messy code. Today, the industry standard is mysql2, which natively supports modern async/await Promises.
bash
1
npm install mysql2

5. Step 2: Creating a Connection Pool

Connecting to a database takes time. If 1,000 users hit your API, opening and closing 1,000 separate database connections will crash your server. Instead, we create a Connection Pool. The pool opens 10 connections and keeps them alive in the background. When a user requests data, the API borrows a connection for a millisecond, does the query, and returns the connection to the pool.

Create a file: config/db.js

javascript
1234567891011121314
const mysql = require('mysql2/promise'); // Note the /promise!

// Create the pool
const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: 'password123',
    database: 'my_store_db',
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0
});

module.exports = pool;

6. Step 3: Executing SQL Queries

Now we import the pool into our Controller and execute raw SQL queries.

In controllers/productController.js:

javascript
12345678910111213141516171819202122232425262728293031323334353637
const db = require('../config/db');

// READ ALL
const getProducts = async (req, res) => {
    try {
        // Execute the raw SQL string
        // mysql2 returns an array where the first item is the rows, and the second is metadata
        const [rows] = await db.query('SELECT * FROM products');
        
        res.status(200).json({ status: "success", data: rows });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
};

// CREATE (Using Parameterized Queries)
const createProduct = async (req, res) => {
    try {
        const { name, price } = req.body;
        
        // CRITICAL: Notice the question marks (?). This prevents SQL Injection!
        const sql = 'INSERT INTO products (name, price) VALUES (?, ?)';
        
        // Pass the variables in an array as the second argument
        const [result] = await db.query(sql, [name, price]);
        
        res.status(201).json({ 
            status: "success", 
            message: "Product created",
            insertId: result.insertId 
        });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
};

module.exports = { getProducts, createProduct };

7. Preventing SQL Injection

Never, ever concatenate strings to build a SQL query. If you write: const sql = "SELECT * FROM users WHERE email = '" + req.body.email + "'"; A hacker can type ' OR 1=1; DROP TABLE users; into the email input box on your website. Your server will execute the command and delete your entire database. ALWAYS use the question mark ? placeholders (Parameterized Queries) as shown in Step 6. The mysql2 package will automatically sanitize the input and neutralize malicious commands.

8. Backend Workflow: Object Relational Mappers (ORMs)

Writing raw SQL strings (like 'SELECT * FROM products') can become very difficult to manage in massive applications with 50 tables. While MongoDB uses Mongoose (an ODM), SQL databases use ORMs (Object Relational Mappers). Popular Node.js ORMs include Sequelize and Prisma. They allow you to write SQL queries using JavaScript methods (e.g., Product.findAll()) instead of raw strings. However, knowing raw SQL via mysql2 is a prerequisite to understanding how ORMs work under the hood.

9. Best Practices

  • Environment Variables: Just like MongoDB, never hardcode your MySQL password into config/db.js. Always use process.env.DB_PASSWORD.

10. Common Mistakes

  • Forgetting Destructuring: The db.query() method returns an array with two things: the data rows, and the field metadata. Beginners often write const data = await db.query(...) and then try to return res.json(data). The frontend receives a massive, confusing object containing thousands of lines of metadata. Always destructure the first item from the array: const [rows] = await db.query(...).

11. Exercises

  1. 1. Explain the architectural and performance benefits of using a Connection Pool (mysql.createPool) versus creating a single connection (mysql.createConnection) for every incoming HTTP request.

12. Coding Challenges

  • Challenge: Write the controller logic for a getProductById function. Use db.query() with a SELECT statement and a WHERE id = ? clause. Extract the specific product from the returned array and send it back as JSON. Handle the case where the array is empty (product not found).

13. MCQs with Answers

Question 1

What is the primary purpose of the question mark (?) placeholder in a mysql2 query like SELECT * FROM users WHERE age = ??

Question 2

When migrating from legacy Node.js code to modern standards, why is the mysql2 package preferred over the older mysql package?

14. Interview Questions

  • Q: Explain the concept of SQL Injection. How does a hacker execute it, and how exactly do parameterized queries prevent it from occurring?
  • Q: Compare and contrast the use case for a relational database (MySQL) versus a document database (MongoDB) when designing the backend for a new web application.

15. FAQs

Q: Can I use PostgreSQL instead of MySQL? A: Absolutely. PostgreSQL is actually preferred by many senior developers. The concepts are identical, but you would install the pg package instead of mysql2.

16. Summary

In Chapter 12, we bridged the gap between Node.js and Relational Databases. We learned how to utilize the modern mysql2 package to create high-performance Connection Pools, execute raw SQL commands asynchronously, and safely extract structured data. Most importantly, we addressed the critical security vulnerability of SQL Injection by enforcing the use of Parameterized Queries.

17. Next Chapter Recommendation

Our database holds user data, but anyone can view it. We need to build a digital lock on our routes. Proceed to Chapter 13: Authentication and Authorization.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·