LearnContact
Lesson 7826 min read

MySQL with Node.js

Learn how to connect a Node.js application to MySQL using the mysql2 package, run parameterized queries, and use the promise-based API with async/await.

Introduction

In the previous lesson you connected a Java application to MySQL using JDBC. Node.js is another extremely common backend environment, powering everything from small APIs to large production services, and it needs its own way to talk to MySQL.

The most popular package for this is mysql2 — a fast, modern MySQL client for Node.js that supports both callback-style and promise-based usage. In this lesson you will install it, connect to a MySQL database, run parameterized queries safely, and use async/await for clean, readable code.

What You Will Learn
  • How to install the mysql2 package with npm.
  • How to create a connection (and later, a connection pool) to MySQL.
  • How to run queries using ? placeholders to prevent SQL injection.
  • How to use the promise-based mysql2/promise API with async/await.
  • How to fetch and insert rows in a small worked example.

Installing mysql2

mysql2 is installed like any other npm package, inside a Node.js project that already has a package.json file.

Terminal
npm install mysql2
Output
added 1 package in 1s
Why mysql2 instead of mysql?

The older mysql package still works but is essentially unmaintained. mysql2 is a drop-in replacement that is faster, actively maintained, and adds first-class Promise support, which is why it is the standard choice today.

Creating a Connection

The simplest way to connect is mysql.createConnection(), passing the host, user, password, and database name.

connect.js
const mysql = require('mysql2');

const connection = mysql.createConnection({
    host: 'localhost',
    user: 'app_user',
    password: 'secret123',
    database: 'school'
});

connection.connect((err) => {
    if (err) {
        console.error('Connection failed:', err.message);
        return;
    }
    console.log('Connected to MySQL successfully!');
});
Output
Connected to MySQL successfully!

This callback-style connection object is straightforward to understand, but as you will see shortly, most real projects prefer the promise-based version for cleaner async code.

Running a Query

The connection.query() method sends SQL to the server. For a SELECT, the callback receives an array of row objects, one per row, with each column already mapped to a JavaScript property.

query.js
connection.query('SELECT id, name, marks FROM students', (err, rows) => {
    if (err) {
        console.error('Query failed:', err.message);
        return;
    }
    rows.forEach((row) => {
        console.log(`${row.id} - ${row.name} - ${row.marks}`);
    });
});
Output
1 - Alex - 78
2 - Sam - 65
3 - Priya - 91

Parameterized Queries with Placeholders

Just like with JDBC, you should never build a SQL string by concatenating user input into it, because that opens the door to SQL injection. mysql2 supports ? placeholders, where values are passed as a separate array and the driver escapes and binds them safely.

placeholders.js
const minMarks = 60;

connection.query(
    'SELECT name, marks FROM students WHERE marks >= ?',
    [minMarks],
    (err, rows) => {
        if (err) {
            console.error('Query failed:', err.message);
            return;
        }
        rows.forEach((row) => {
            console.log(`${row.name} - ${row.marks}`);
        });
    }
);
Output
Alex - 78
Priya - 91
Never Concatenate User Input into SQL

Writing `SELECT * FROM students WHERE name = '${userInput}'` lets an attacker supply input like ' OR '1'='1 to change your query's meaning entirely. Always pass values through the ? placeholder array instead.

The Promise-Based API with async/await

Callback-based code becomes awkward once you need to run several queries in sequence. mysql2/promise exposes the same functionality wrapped in Promises, so you can use async/await for cleaner, more readable code.

promiseConnect.js
const mysql = require('mysql2/promise');

async function main() {
    const connection = await mysql.createConnection({
        host: 'localhost',
        user: 'app_user',
        password: 'secret123',
        database: 'school'
    });

    const [rows] = await connection.query(
        'SELECT name, marks FROM students WHERE marks >= ?',
        [60]
    );

    rows.forEach((row) => {
        console.log(`${row.name} - ${row.marks}`);
    });

    await connection.end();
}

main().catch((err) => console.error('Error:', err.message));
Output
Alex - 78
Priya - 91

connection.query() with the promise API returns an array where the first element is the rows (or, for an INSERT/UPDATE, a result object) and the second is field metadata — which is why the example destructures [rows] from the returned array.

Connection Pools

Opening a brand-new connection for every request is wasteful. A connection pool keeps a set of reusable connections open and hands them out as needed, which is what real applications should use instead of a single raw connection.

pool.js
const mysql = require('mysql2/promise');

const pool = mysql.createPool({
    host: 'localhost',
    user: 'app_user',
    password: 'secret123',
    database: 'school',
    waitForConnections: true,
    connectionLimit: 10
});

async function getStudents() {
    const [rows] = await pool.query('SELECT * FROM students');
    return rows;
}
Note

A pool exposes the same .query() and .execute() methods as a single connection, so switching from createConnection to createPool usually requires very little code change beyond how the connection object is created.

A Worked Example

Let's combine everything: fetch existing students, then insert a new one, all using the promise API and parameterized queries.

app.js
const mysql = require('mysql2/promise');

async function main() {
    const pool = mysql.createPool({
        host: 'localhost',
        user: 'app_user',
        password: 'secret123',
        database: 'school',
        connectionLimit: 10
    });

    const [existing] = await pool.query('SELECT name, marks FROM students');
    console.log('Existing students:');
    existing.forEach((row) => console.log(` - ${row.name}: ${row.marks}`));

    const [result] = await pool.query(
        'INSERT INTO students (name, marks) VALUES (?, ?)',
        ['Jordan', 88]
    );
    console.log(`Inserted student with id ${result.insertId}`);

    await pool.end();
}

main().catch((err) => console.error('Error:', err.message));
Output
Existing students:
 - Alex: 78
 - Sam: 65
 - Priya: 91
Inserted student with id 4

result.insertId gives you the auto-generated primary key of the row you just inserted, which is useful whenever your application needs to immediately reference the new record.

Common Mistakes

Avoid These Mistakes
  • Building SQL strings with template literals or concatenation instead of using ? placeholders.
  • Opening a new connection per request instead of using a shared connection pool.
  • Forgetting to await a query in async code, causing it to run out of order or be silently ignored.
  • Never calling connection.end() or pool.end(), leaving connections open longer than necessary.
  • Mixing the callback API and the promise API inconsistently within the same project.

Best Practices

  • Prefer mysql2/promise with async/await over the callback-based API for new code.
  • Always use ? placeholders for any value that comes from outside your code.
  • Use a connection pool in real applications rather than a single raw connection.
  • Keep credentials in environment variables, not hardcoded in source files.
  • Wrap database calls in try/catch so failures are handled and logged instead of crashing silently.

Frequently Asked Questions

What is the difference between mysql and mysql2?

mysql2 is a modern, actively maintained, faster alternative to the older mysql package, with the same core API plus built-in Promise support, which is why it is the standard recommendation today.

Should I use .query() or .execute()?

.query() supports both plain SQL and placeholders. .execute() uses MySQL prepared statements under the hood, which can be slightly faster for queries run repeatedly, but .query() with placeholders is safe and sufficient for most use cases.

Do I need a connection pool for a small script?

For a one-off script, a single connection is fine. For any long-running server handling multiple requests, a pool is strongly recommended so requests do not have to wait for a single connection to free up.

How do placeholders actually prevent SQL injection?

Values passed alongside ? placeholders are sent to MySQL separately from the SQL text and are treated strictly as data, never as executable SQL, so special characters in user input cannot alter the query's structure.

Can I use mysql2 with frameworks like Express?

Yes. mysql2 (often through a connection pool) is commonly used directly inside Express route handlers, and it also works underneath many higher-level ORMs.

Key Takeaways

  • mysql2 is the standard, actively maintained package for connecting Node.js to MySQL.
  • A connection or pool is created with createConnection() or createPool(), passing host, user, password, and database.
  • ? placeholders keep queries safe from SQL injection by separating SQL structure from data.
  • mysql2/promise exposes a Promise-based API that pairs naturally with async/await.
  • Connection pools are the recommended way to manage database connections in real applications.

Summary

mysql2 makes it straightforward to connect a Node.js application to MySQL, whether through a single connection or a shared pool. Combined with ? placeholders for safety and the promise-based API for clean async code, it gives you everything needed to build real database-backed applications.

In this lesson, you installed mysql2, connected to MySQL, ran safe parameterized queries, and used async/await to fetch and insert data. In the final lesson of this course, you will pull together everything you have learned into a consolidated set of MySQL best practices.

Lesson 78 Completed
  • You can install and use the mysql2 package in a Node.js project.
  • You can create a connection or connection pool to MySQL.
  • You can run safe, parameterized queries using ? placeholders.
  • You can use mysql2/promise with async/await for cleaner code.
  • You have fetched and inserted rows in a complete worked example.
Next Lesson →

MySQL Best Practices