Setting Up Docker and MySQL for React Projects
Setting Up Docker and MySQL
~14 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Our reviews so far live only in React state – gone after every reload. In this and the next chapter, we'll build our own small server plus a database, so reviews are actually saved permanently. The first step: Docker and a MySQL database.
What exactly is Docker?
A database like MySQL is really a standalone program you'd normally have to laboriously install, configure, and maintain on your computer – with version conflicts, OS differences, and a lot of manual setup. Docker solves this by packaging programs into "containers": ready-configured, isolated mini-environments that run identically on any computer, whether Windows, Mac, or Linux.
| Docker term | Comparison |
|---|---|
| Image – the "blueprint"/"recipe" for a container, e.g. "MySQL version 8" | comparable to a .dmg/.exe installer file, just reproducible and identical for everyone |
| Container – a RUNNING instance of an image | comparable to a running program/process – you can start multiple containers from the same image |
| docker-compose.yml – a config file describing several related containers together | comparable to a package.json, just for whole programs/services instead of npm packages |
Creating docker-compose.yml
In the root folder (at the same level as package.json, NOT inside src/), create the file docker-compose.yml:
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secretpassword
MYSQL_DATABASE: produktkatalog
ports:
- '3306:3306'
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:Line by line: image: mysql:8.0 automatically downloads the official, ready-configured MySQL image from Docker Hub (the "app store" for container images). environment sets environment variables that the MySQL image itself reads to automatically create a root password and an empty database called produktkatalog on its very first start. ports: '3306:3306' connects port 3306 INSIDE the container to port 3306 ON your computer, so programs outside the container (our upcoming Node server) can connect. volumes ensures the database files survive a docker compose down, instead of being deleted along with the container.
Starting MySQL
docker compose up -d-d ("detached") starts the containers in the background instead of blocking your terminal. Verify it's running:
docker compose psYou should see an entry with status "running" or "Up". To stop it later: docker compose down (the database DATA survives thanks to volumes, only the running container gets stopped).
server/: laying the foundation for our Node server
Our own server lives in its own folder, server/, completely separate from src/ (that's React code that runs in the BROWSER) – the server code, by contrast, runs directly with Node.js, no browser involved. Create server/package.json:
{
"name": "produktkatalog-server",
"type": "module",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.19.0",
"mysql2": "^3.9.0",
"cors": "^2.8.5"
}
}"type": "module" enables modern import/export syntax instead of the older require() – the same syntax you've already been using throughout src/. Install the three packages:
cd server
npm install
cd ..express is the most common web server framework for Node.js (comparable to Symfony/Laravel routing in the PHP world, just noticeably more minimal). mysql2 is the database driver we use to connect from Node.js to our freshly started MySQL container. cors allows our React app (running on localhost:5173) to make requests to our server (running on a different port) – without cors, the browser blocks that by default for security reasons.
server/db.js: the database connection
import mysql from 'mysql2/promise';
export const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'secretpassword',
database: 'produktkatalog',
});mysql2/promise instead of mysql2 matters – the "promise" variant supports async/await from chapter 22, instead of only callback functions. A "pool" automatically manages several reusable database connections in the background, instead of opening and closing a new connection for every request – noticeably faster.
server/server.js: the skeleton
import express from 'express';
import cors from 'cors';
import { pool } from './db.js';
const app = express();
app.use(cors());
app.use(express.json());
app.get('/api/health', async (req, res) => {
const [rows] = await pool.query('SELECT 1 AS ok');
res.json({ status: 'ok', databaseConnected: rows[0].ok === 1 });
});
app.listen(4000, () => {
console.log('Server running at http://localhost:4000');
});app.use(express.json()) makes Express automatically turn incoming JSON requests into req.body (we'll need that in the next chapter for new reviews). Start the server and test the health check:
cd server
node server.jsIn a second terminal (or browser tab), open http://localhost:4000/api/health – you should see {{"status":"ok","databaseConnected":true}}. That confirms: both the Node server AND the MySQL connection are working.
Target project structure (reminder from chapter 3) – server/ is now taking shape
produktkatalog-web/
├── index.html
├── package.json
├── vite.config.js
├── docker-compose.yml (MySQL container for reviews)
├── server/ (our own Node/Express server)
│ ├── package.json
│ ├── server.js
│ └── db.js
└── src/
├── main.jsx
├── App.jsx (routing)
├── index.css
├── api/
│ ├── magentoApi.js (Magento REST API: products)
│ └── reviewsApi.js (our own server: reviews)
├── context/
│ └── AuthContext.jsx (simple login state)
├── components/
│ ├── ProductCard.jsx
│ └── ProtectedRoute.jsx
├── hooks/
│ └── useDocumentTitle.js (custom hook)
└── pages/
├── ProductListPage.jsx
├── ProductDetailPage.jsx
├── ReviewsPage.jsx (nested route)
├── LoginPage.jsx
└── AccountPage.jsx (protected route)Achtung: A real password should NEVER go directly into code, as we do here for simplicity – in a real project you'd store credentials in a .env file (NOT checked into your Git repository) and load them via the dotenv package. For this local learning setup, the hard-coded password is a deliberate simplification.