HomeDatabaseBuild a Real-Time Node.js App with MySQL Database

Build a Real-Time Node.js App with MySQL Database

This comprehensive guide details integrating a MySQL database into a real-time Node.js and Socket.io application. You will learn practical steps to create a persistent voting system where data survives server restarts, designed for developers seeking to enhance their real-time apps with reliable data storage.

In previous tutorials, real-time Node.js applications stored data only in volatile memory, losing it on server reboot. For production, persistent storage is essential. The MySQL database is chosen for its reliability and widespread use. Therefore, this guide focuses on integrating MySQL to build a robust real-time Node.js application.

This project extends a basic real-time voting application tutorial. Ensure your Node.js development environment and base project are set up before proceeding.

Steps to Integrate MySQL into a Real-Time Node.js App

Database integration involves several key sequential steps. First, ensure MySQL is installed on your system by downloading the official installer from the MySQL website. Then, create a dedicated database and table for this project.

1. Create the Voting Database Schema

The table schema defines the data storage structure. Execute the following SQL command in a MySQL client like MySQL Workbench or phpMyAdmin. This table will log each vote with an automatic timestamp.

CREATE TABLE `voting` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(12) NOT NULL,
  `time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Schema Explanation: The id column is an auto-incrementing primary key. The name column stores the candidate’s name (e.g., “akbar” or “goldie”). The time column automatically records the vote timestamp. The InnoDB engine is preferred for its transaction and foreign key support.

2. Install the MySQL Driver for Node.js

Node.js requires a specific driver to communicate with MySQL. The mysql2 package is recommended over mysql as it natively supports Promises, leading to cleaner, more manageable code. Open a terminal in your project directory and run the following command.

npm install mysql2

This installation adds the driver to your project’s package.json file. Ensure you are in the same directory as your server.js file when running this command.

3. Configure the Database Connection in server.js

The next step is to edit the server.js file. Create a connection pool to the MySQL database using the installed driver. A pool configuration is more efficient for handling concurrent connections than a single connection.

const mysql = require('mysql2/promise');

// Create a MySQL connection pool
const pool = mysql.createPool({
    host: 'localhost',
    user: 'root', // Replace with your MySQL username
    password: '', // Replace with your MySQL password
    database: 'your_database_name', // Replace with your database name
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0
});

Configuration Details: The connectionLimit: 10 option sets the maximum number of connections in the pool. waitForConnections: true allows requests to wait if the pool is full. queueLimit: 0 means no queue limit. Always use environment variables to store database credentials in production.

4. Create Functions to Retrieve and Store Data

The application requires two main functions. The first initializes data when the server starts. The second handles saving new data each time a user votes. Using async/await results in more readable code compared to callbacks.

// Global variable for in-memory data
let voteCount = { akbar: 0, goldie: 0 };

// Function to initialize data from the database
async function initData() {
    try {
        const [rows] = await pool.query('SELECT `name`, COUNT(*) AS `total` FROM `voting` GROUP BY `name`');
        rows.forEach(row => {
            voteCount[row.name] = row.total;
        });
        console.log('Data successfully initialized from database.');
    } catch (error) {
        console.error('Failed to initialize data:', error);
    }
}

// Function to add a new vote
async function addVote(candidateName) {
    try {
        await pool.query('INSERT INTO voting (name) VALUES (?)', [candidateName]);
        voteCount[candidateName]++;
        return true;
    } catch (error) {
        console.error('Failed to save vote:', error);
        return false;
    }
}

Function Details: The initData() function uses a GROUP BY query to count total votes per candidate, loading the results into the voteCount object. The addVote() function uses a prepared statement (?) to prevent SQL injection. Both functions include basic error handling with try-catch blocks.

5. Integrate Database Functions with Socket.io

Integration occurs within the Socket.io connection event. When a client connects, the server sends the latest data. The server also listens for vote submission events from clients. This pattern ensures all clients receive real-time updates.

io.on('connection', async (socket) => {
    console.log('Client connected');

    // Send current data to the newly connected client
    socket.emit('updateData', voteCount);

    // Handle vote event from client
    socket.on('submitVote', async (candidate) => {
        const success = await addVote(candidate);
        if (success) {
            // Broadcast updated data to ALL connected clients
            io.emit('updateData', voteCount);
        } else {
            socket.emit('voteError', 'Failed to save vote.');
        }
    });
});

// Initialize data when the server starts
initData();

Event Flow: First, on client connection, the server emits an updateData event with current data. Second, when a client emits a submitVote event, the server processes it to the database. If successful, the server broadcasts the update to all clients. If it fails, only the submitting client receives an error notification.

6. Optimization and Best Practices for Production

For a production environment, consider key optimizations. First, use environment variables for database configuration. Second, implement retry logic for database connections. Third, consider using Redis cache in front of MySQL for very high-traffic scenarios.

// Example using environment variables with dotenv
require('dotenv').config();
const pool = mysql.createPool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME
});

Additionally, always validate client input before database processing. For a voting application, ensure the received candidate value matches a valid option.

By following these steps, your real-time Node.js application now features permanent data storage using a MySQL database. The code structure is more organized and ready for further development.

As a next step, you can add features like user authentication, logging, or more complex real-time data visualization. Always refer to the official Node.js documentation and mysql2 documentation for best practices and updates. This implementation provides a solid foundation for real-time applications requiring data persistence.

Latest Articles