Ravindra BagaleCourses & study guides मराठी

Chapter 57: Docker Projects Part 1: Websites, Databases, LAMP, LEMP, WordPress, 3-Tier and Every Language with MySQL and MongoDB

57.6 Project 6: Node.js with MySQL and with MongoDB, plus a Redis cache

Why: the problem

Suppose we are at Swiggy and a small team builds an orders API in Node.js. The restaurant team stores orders in MySQL because the data has a fixed shape. The offers team stores the same kind of data in MongoDB because each offer has different fields. Both teams read the same menu again and again, so the database gets the same query thousands of times per minute. Without Docker every developer installs Node 20, MySQL 8, MongoDB 7 and Redis on the laptop, and versions never match the server. With Docker we write one small Express app, one Dockerfile, and two Compose files: one for MySQL and one for MongoDB. Then we add Redis in front of the database as a cache, and we measure how much faster the second request is. By the end we have one Node image, two databases, one cache, and the habit of switching stacks with only environment variables.

What: every term in plain English

  • Express — A small web framework for Node.js. It maps a URL like GET /orders to a function.
  • mysql2 — The popular MySQL driver for Node.js. It supports promises and connection pools.
  • mongodb driver — The official MongoDB driver for Node.js.
  • Redis — An in-memory key-value store. We use it as a cache in front of the database.
  • Cache-aside — The app first looks in Redis. On a miss it reads the database and saves the answer in Redis with a time limit (TTL).
  • TTL — Time to live. Redis deletes the key after this many seconds.
  • npm ci — Installs exactly what package-lock.json says. Faster and repeatable in Docker.
  • Init script — A .sql or .js file in /docker-entrypoint-initdb.d. The database image runs it only on the first start with an empty volume.
Write the Express app with two storage files. Write the Dockerfile. Run with MySQL. Run with MongoDB. Add Redis as a cache. Test with curl and measure. Clean up.
Node orders API to MySQL or MongoDB with Redis

How: step by step

  1. Write the Express app with two storage files. store-mysql.js and store-mongo.js.
  2. Write the Dockerfile. node:20-alpine, npm ci, non-root user node.
  3. Run with MySQL. compose.mysql.yaml with an init script.
  4. Run with MongoDB. compose.mongo.yaml with an init script.
  5. Add Redis as a cache. compose.cache.yaml merged on top.
  6. Test with curl and measure. First call is a miss, second is a hit.
  7. Clean up. down -v for each project.

Architecture

curl / browser: localhost:3000 node-orders:1.0 (Express) DB_KIND chooses the store Redis 7 cache (optional, TTL 30 s) MySQL 8.4 (mysql2) or MongoDB 7 (driver) Named volume per database
Node orders API architecture

Folder layout

node-orders/
  src/
    server.js
    store-mysql.js
    store-mongo.js
    cache.js
  sql/mysql-init.sql
  mongo/init.js
  package.json
  package-lock.json
  Dockerfile
  .dockerignore
  .env
  compose.mysql.yaml
  compose.mongo.yaml
  compose.cache.yaml

Example 1: The Node app, both storage files and the Dockerfile

Goal. The Node app, both storage files and the Dockerfile.

package.json

{
  "name": "node-orders",
  "version": "1.0.0",
  "main": "src/server.js",
  "scripts": { "start": "node src/server.js" },
  "dependencies": {
    "express": "4.21.1",
    "mysql2": "3.11.3",
    "mongodb": "6.10.0",
    "redis": "4.7.0"
  }
}

src/server.js

const express = require("express");
const kind = process.env.DB_KIND || "mysql";
const store = require(kind === "mongo" ? "./store-mongo" : "./store-mysql");
const cache = require("./cache");

const app = express();
app.use(express.json());

app.get("/healthz", async (req, res) => {
  try {
    await store.ping();
    res.json({ status: "ok", db: kind, cache: cache.enabled });
  } catch (e) {
    res.status(503).json({ status: "down", error: e.message });
  }
});

app.get("/menu", async (req, res) => {
  const hit = await cache.get("menu");
  if (hit) return res.json({ from: "cache", items: hit });
  const items = await store.menu();
  await cache.set("menu", items, 30);
  res.json({ from: "db", items });
});

app.post("/orders", async (req, res) => {
  const { item, qty } = req.body;
  if (!item || !qty) return res.status(400).json({ error: "item and qty are required" });
  res.status(201).json(await store.addOrder(item, Number(qty)));
});

app.get("/orders", async (req, res) => res.json(await store.orders()));

app.listen(3000, "0.0.0.0", () => console.log("orders api on 3000, db=" + kind));

src/store-mysql.js

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

const pool = mysql.createPool({
  host: process.env.DB_HOST || "db",
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  connectionLimit: 5
});

exports.ping = async () => { await pool.query("SELECT 1"); };
exports.menu = async () => (await pool.query("SELECT name, price FROM menu ORDER BY name"))[0];
exports.orders = async () => (await pool.query("SELECT id, item, qty FROM orders ORDER BY id"))[0];
exports.addOrder = async (item, qty) => {
  const [r] = await pool.query("INSERT INTO orders (item, qty) VALUES (?, ?)", [item, qty]);
  return { id: r.insertId, item, qty };
};

src/store-mongo.js

const { MongoClient } = require("mongodb");

const client = new MongoClient(process.env.MONGO_URL);
const ready = client.connect();
const db = async () => { await ready; return client.db("swiggy"); };

exports.ping = async () => { await (await db()).command({ ping: 1 }); };
exports.menu = async () => (await db()).collection("menu").find({}, { projection: { _id: 0 } }).sort({ name: 1 }).toArray();
exports.orders = async () => (await db()).collection("orders").find({}, { projection: { _id: 0 } }).toArray();
exports.addOrder = async (item, qty) => {
  const r = await (await db()).collection("orders").insertOne({ item, qty });
  return { id: r.insertedId.toString(), item, qty };
};

src/cache.js

const { createClient } = require("redis");
const url = process.env.REDIS_URL;
let client = null;
if (url) {
  client = createClient({ url });
  client.on("error", (e) => console.log("redis error", e.message));
  client.connect();
}
exports.enabled = Boolean(url);
exports.get = async (k) => (client ? JSON.parse((await client.get(k)) || "null") : null);
exports.set = async (k, v, ttl) => { if (client) await client.set(k, JSON.stringify(v), { EX: ttl }); };

Dockerfile

FROM node:20-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src/ ./src/
USER node
EXPOSE 3000
HEALTHCHECK --interval=10s --timeout=3s --retries=5 \
  CMD wget -qO- http://localhost:3000/healthz || exit 1
CMD ["node", "src/server.js"]

.dockerignore

node_modules
.env
.git
*.log

What each line does

  • DB_KIND — Chooses which storage file is loaded at start-up.
  • mysql.createPool — Keeps up to 5 open connections and reuses them.
  • ? placeholders — Values are sent separately from the SQL text, so SQL injection is not possible.
  • cache.get / cache.set — Cache-aside: read Redis first, then the database, then save to Redis for 30 seconds.
  • npm ci --omit=dev — Installs only production packages exactly as in package-lock.json.
  • USER node — The node image already has a non-root user called node.
  • HEALTHCHECK wget — Alpine has wget, so no extra package is needed for the health check.

Green output

$ npm install --package-lock-only
added 92 packages in 3s
$ docker build -t node-orders:1.0 .
 => [4/5] RUN npm ci --omit=dev
 => exporting to image
 => naming to docker.io/library/node-orders:1.0
$ docker image ls node-orders
REPOSITORY    TAG   IMAGE ID       SIZE
node-orders   1.0   4b1e9c0a7d22   168MB

Expected result. One image of about 168 MB that can talk to MySQL or MongoDB, with an optional Redis cache.

Example 2: Run the Node app with MySQL

Goal. Run the Node app with MySQL.

sql/mysql-init.sql

CREATE TABLE menu (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  price INT NOT NULL
);
CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  item VARCHAR(100) NOT NULL,
  qty INT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO menu (name, price) VALUES ('Masala Dosa', 120), ('Paneer Roll', 150), ('Veg Biryani', 220);

.env

MYSQL_ROOT_PASSWORD=change-me-root
DB_USER=orders
DB_PASSWORD=change-me-app
DB_NAME=orders

compose.mysql.yaml

services:
  api:
    build: .
    image: node-orders:1.0
    ports:
      - "3000:3000"
    environment:
      DB_KIND: mysql
      DB_HOST: db
      DB_USER: ${DB_USER}
      DB_PASSWORD: ${DB_PASSWORD}
      DB_NAME: ${DB_NAME}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
  db:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
    volumes:
      - mysql-data:/var/lib/mysql
      - ./sql/mysql-init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 5s
      retries: 20
volumes:
  mysql-data:

What each line does

  • ${DB_USER} — Compose reads the value from the .env file next to compose.mysql.yaml.
  • docker-entrypoint-initdb.d — MySQL runs 01-init.sql once, only when mysql-data is empty.
  • :ro — The container can read the init file but not change it.
  • condition: service_healthy — The API starts only after mysqladmin ping succeeds.
  • restart: unless-stopped — If Node crashes, Docker starts it again.

Green output

$ docker compose -f compose.mysql.yaml -p orders-mysql up -d --wait
 Container orders-mysql-db-1   Healthy
 Container orders-mysql-api-1  Healthy
$ curl -s localhost:3000/healthz
{"status":"ok","db":"mysql","cache":false}
$ curl -s -X POST localhost:3000/orders -H 'Content-Type: application/json' -d '{"item":"Masala Dosa","qty":2}'
{"id":1,"item":"Masala Dosa","qty":2}
$ curl -s localhost:3000/menu
{"from":"db","items":[{"name":"Masala Dosa","price":120},{"name":"Paneer Roll","price":150},{"name":"Veg Biryani","price":220}]}

Expected result. The API answers from MySQL. The menu comes from the init script, and orders are saved in the mysql-data volume.

Example 3: Run the same image with MongoDB

Goal. Run the same image with MongoDB.

mongo/init.js

db = db.getSiblingDB("swiggy");
db.menu.insertMany([
  { name: "Masala Dosa", price: 120 },
  { name: "Paneer Roll", price: 150 },
  { name: "Veg Biryani", price: 220, spicy: true }
]);
db.orders.createIndex({ item: 1 });

compose.mongo.yaml

services:
  api:
    build: .
    image: node-orders:1.0
    ports:
      - "3000:3000"
    environment:
      DB_KIND: mongo
      MONGO_URL: mongodb://root:${MONGO_PASSWORD}@mongo:27017/?authSource=admin
    depends_on:
      mongo:
        condition: service_healthy
  mongo:
    image: mongo:7
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
    volumes:
      - mongo-data:/data/db
      - ./mongo/init.js:/docker-entrypoint-initdb.d/init.js:ro
    healthcheck:
      test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
      interval: 5s
      retries: 20
volumes:
  mongo-data:

What each line does

  • MONGO_URL — The full connection string. authSource=admin is needed because root lives in the admin database.
  • getSiblingDB — Switches the init script to the swiggy database.
  • spicy: true — Only one document has this field. MongoDB allows different shapes.
  • createIndex — Makes searches by item fast.
  • mongosh --eval — The health check asks Mongo to answer ping.

Green output

$ echo 'MONGO_PASSWORD=change-me-mongo' >> .env
$ docker compose -f compose.mongo.yaml -p orders-mongo up -d --wait
 Container orders-mongo-mongo-1  Healthy
 Container orders-mongo-api-1    Healthy
$ curl -s localhost:3000/healthz
{"status":"ok","db":"mongo","cache":false}
$ curl -s -X POST localhost:3000/orders -H 'Content-Type: application/json' -d '{"item":"Paneer Roll","qty":1}'
{"id":"6718f2c4a1b2c3d4e5f60718","item":"Paneer Roll","qty":1}

Expected result. Same image, no rebuild. Only DB_KIND and MONGO_URL changed.

Example 4: Add Redis as a cache and measure the speed

Goal. Add Redis as a cache and measure the speed.

compose.cache.yaml

services:
  api:
    environment:
      REDIS_URL: redis://redis:6379
    depends_on:
      redis:
        condition: service_healthy
  redis:
    image: redis:7-alpine
    command: ["redis-server", "--maxmemory", "64mb", "--maxmemory-policy", "allkeys-lru"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      retries: 10

What each line does

  • -f a -f b — Compose merges the second file on top of the first. The api service gets REDIS_URL added.
  • --maxmemory 64mb — Redis never uses more than 64 MB.
  • allkeys-lru — When full, Redis removes the least recently used keys first.
  • redis-cli ping — Answers PONG when Redis is ready.
  • TTL menu — Shows how many seconds are left before the cached menu expires.

Green output

$ docker compose -f compose.mysql.yaml -f compose.cache.yaml -p orders-mysql up -d --wait
 Container orders-mysql-redis-1  Healthy
 Container orders-mysql-api-1    Healthy
$ curl -s -w ' %{time_total}s\n' localhost:3000/menu | tail -c 40
...{"name":"Veg Biryani","price":220}]} 0.0182s
$ curl -s -w ' %{time_total}s\n' localhost:3000/menu | cut -c1-20
{"from":"cache","ite 0.0031s
$ docker compose -p orders-mysql exec redis redis-cli TTL menu
(integer) 24

Expected result. The first call reads MySQL (from db). The second comes from Redis (from cache) and is about 6 times faster.

Every command, step by step (plain docker and Compose)

  1. Make folders

    mkdir -p node-orders/src node-orders/sql node-orders/mongo && cd node-orders
    

    What it does: Creates the project folders. Then add every file from the examples.

    (no output)
    
  2. Create the lock file

    docker run --rm -v "$PWD":/app -w /app node:20-alpine npm install --package-lock-only
    

    What it does: Makes package-lock.json without installing Node on the laptop.

    added 92 packages in 3s
    
  3. Build the image

    docker build -t node-orders:1.0 .
    

    What it does: Builds the Node image once for both stacks.

    naming to docker.io/library/node-orders:1.0
    
  4. Check the user

    docker run --rm node-orders:1.0 whoami
    

    What it does: Must print node, not root.

    node
    
  5. Start the MySQL stack

    docker compose -f compose.mysql.yaml -p orders-mysql up -d --wait
    

    What it does: Starts MySQL, waits for healthy, then the API.

    Container orders-mysql-api-1 Healthy
    
  6. See the containers

    docker compose -p orders-mysql ps
    

    What it does: Shows both services and their health.

    NAME                 STATUS
    orders-mysql-api-1   Up 20 seconds (healthy)
    orders-mysql-db-1    Up 31 seconds (healthy)
    
  7. Read the logs

    docker compose -p orders-mysql logs api --tail 5
    

    What it does: Shows the start message from Node.

    api-1  | orders api on 3000, db=mysql
    
  8. Look inside MySQL

    docker compose -p orders-mysql exec db mysql -uorders -pchange-me-app orders -e 'SELECT * FROM orders'
    

    What it does: Proves the order row is really in the database.

    id  item         qty  created_at
    1   Masala Dosa  2    2026-10-25 10:12:03
    
  9. Add the cache

    docker compose -f compose.mysql.yaml -f compose.cache.yaml -p orders-mysql up -d --wait
    

    What it does: Adds Redis and recreates the API with REDIS_URL.

    Container orders-mysql-redis-1 Healthy
    
  10. Stop MySQL and start Mongo

    docker compose -f compose.mysql.yaml -f compose.cache.yaml -p orders-mysql down
    docker compose -f compose.mongo.yaml -p orders-mongo up -d --wait
    

    What it does: Frees port 3000 and runs the same image on MongoDB.

    Container orders-mongo-api-1 Healthy
    
  11. Look inside Mongo

    docker compose -p orders-mongo exec mongo mongosh -u root -p change-me-mongo --quiet --eval 'db.getSiblingDB("swiggy").orders.find()'
    

    What it does: Shows the saved order documents.

    [ { _id: ObjectId('6718f2c4...'), item: 'Paneer Roll', qty: 1 } ]
    
  12. Clean up

    docker compose -f compose.mongo.yaml -p orders-mongo down -v
    docker compose -f compose.mysql.yaml -f compose.cache.yaml -p orders-mysql down -v
    docker image rm node-orders:1.0
    

    What it does: Removes both stacks, their volumes and the image.

    Volume orders-mysql_mysql-data Removed
    Untagged: node-orders:1.0
    

When it is red 1

Error: connect ECONNREFUSED 172.20.0.2:3306

Cause. The API started before MySQL was ready, or depends_on has no condition.

Fix, step by step

  1. Add condition: service_healthy under depends_on.
  2. Check docker compose ps shows db as healthy.
  3. Run docker compose up -d again.

When it is red 2

Error: Access denied for user 'orders'@'172.20.0.3' (using password: YES)

Cause. The password in .env was changed after the volume was first created. MySQL keeps the old user.

Fix, step by step

  1. Practice data only: docker compose -p orders-mysql down -v.
  2. Start again so the init runs with the new password.
  3. On real data, change the password with ALTER USER instead.

When it is red 3

Error: Table 'orders.menu' doesn't exist

Cause. The init script did not run because the volume already had data from an older run.

Fix, step by step

  1. Run docker compose -p orders-mysql down -v on practice data.
  2. Check the file is mounted: docker compose exec db ls /docker-entrypoint-initdb.d.
  3. Start again and read the db logs for the init lines.

When it is red 4

npm ERR! The `npm ci` command can only install with an existing package-lock.json

Cause. package-lock.json was not created or is in .dockerignore.

Fix, step by step

  1. Create it with the npm install --package-lock-only step.
  2. Make sure .dockerignore does not list package-lock.json.
  3. Build again.

When it is red 5

redis error connect ECONNREFUSED 172.20.0.4:6379

Cause. REDIS_URL is set but Redis is not running or not healthy yet.

Fix, step by step

  1. Use the compose.cache.yaml file that also starts redis.
  2. Check docker compose ps shows redis healthy.
  3. The app keeps working without cache because errors are only logged.

Practice tasks

Easy Task 1: Build

Why: Start.

What to do: Build node-orders:1.0 and check the user.

How:
  1. Use a VM or laptop with Docker and Compose.
  2. Create the node-orders folder with the files from this lesson.
  3. Run the steps and compare with the solution.
Solution
docker build -t node-orders:1.0 .
docker run --rm node-orders:1.0 whoami

Easy Task 2: MySQL stack

Why: Basics.

What to do: Start the MySQL stack and add two orders.

How:
  1. Use a VM or laptop with Docker and Compose.
  2. Create the node-orders folder with the files from this lesson.
  3. Run the steps and compare with the solution.
Solution
docker compose -f compose.mysql.yaml -p orders-mysql up -d --wait
curl -s -X POST localhost:3000/orders -H 'Content-Type: application/json' -d '{"item":"Veg Biryani","qty":1}'

Easy Task 3: Mongo stack

Why: Same image.

What to do: Run the Mongo stack without rebuilding.

How:
  1. Use a VM or laptop with Docker and Compose.
  2. Create the node-orders folder with the files from this lesson.
  3. Run the steps and compare with the solution.
Solution
docker compose -f compose.mongo.yaml -p orders-mongo up -d --wait
curl -s localhost:3000/healthz

Medium Task 4: Cache hit

Why: Speed.

What to do: Add Redis and show from:db then from:cache.

How:
  1. Use a VM or laptop with Docker and Compose.
  2. Create the node-orders folder with the files from this lesson.
  3. Run the steps and compare with the solution.
Solution
docker compose -f compose.mysql.yaml -f compose.cache.yaml -p orders-mysql up -d --wait
curl -s localhost:3000/menu | cut -c1-20
curl -s localhost:3000/menu | cut -c1-20

Medium Task 5: Data survives

Why: Volumes.

What to do: Remove the containers (not volumes) and prove the orders are still there.

How:
  1. Use a VM or laptop with Docker and Compose.
  2. Create the node-orders folder with the files from this lesson.
  3. Run the steps and compare with the solution.
Solution
docker compose -f compose.mysql.yaml -p orders-mysql down
docker compose -f compose.mysql.yaml -p orders-mysql up -d --wait
curl -s localhost:3000/orders

Medium Task 6: Bad input

Why: Validation.

What to do: Send an order without qty and read the 400 error.

How:
  1. Use a VM or laptop with Docker and Compose.
  2. Create the node-orders folder with the files from this lesson.
  3. Run the steps and compare with the solution.
Solution
curl -s -X POST localhost:3000/orders -H 'Content-Type: application/json' -d '{"item":"Dosa"}'

Medium Task 7: Redis down

Why: Fault tolerance.

What to do: Stop Redis and prove the API still answers from the database.

How:
  1. Use a VM or laptop with Docker and Compose.
  2. Create the node-orders folder with the files from this lesson.
  3. Run the steps and compare with the solution.
Solution
docker compose -p orders-mysql stop redis
curl -s localhost:3000/menu | cut -c1-20

Hard Task 8: Backup

Why: Safety.

What to do: Dump the orders database to a file on the host.

How:
  1. Use a VM or laptop with Docker and Compose.
  2. Create the node-orders folder with the files from this lesson.
  3. Run the steps and compare with the solution.
Solution
docker compose -p orders-mysql exec db mysqldump -uroot -pchange-me-root orders > orders-backup.sql
ls -lh orders-backup.sql

Hard Task 9: Scale

Why: Many replicas.

What to do: Remove the fixed host port and run 3 API replicas.

How:
  1. Use a VM or laptop with Docker and Compose.
  2. Create the node-orders folder with the files from this lesson.
  3. Run the steps and compare with the solution.
Solution
# change ports to - "3000" in compose.mysql.yaml
docker compose -f compose.mysql.yaml -p orders-mysql up -d --scale api=3
docker compose -p orders-mysql ps

Easy Task 10: Clean

Why: Habit.

What to do: Remove both stacks and the image.

How:
  1. Use a VM or laptop with Docker and Compose.
  2. Create the node-orders folder with the files from this lesson.
  3. Run the steps and compare with the solution.
Solution
docker compose -f compose.mongo.yaml -p orders-mongo down -v
docker compose -f compose.mysql.yaml -f compose.cache.yaml -p orders-mysql down -v
docker image rm node-orders:1.0

Homework: try at home

Add GET /orders/:id with a Redis cache of 60 seconds, delete the cache key when a new order is added, and show the from field change from db to cache in both the MySQL and MongoDB stacks.

Recap

  • One Node image works with MySQL or MongoDB by environment variables.
  • Init scripts seed the database only on the first start.
  • Health checks plus depends_on stop the API starting too early.
  • Redis cache-aside makes repeated reads much faster.
  • A second -f file adds services without editing the first.
  • down -v removes practice data; plain down keeps it.

FAQ

Q. Why node:20-alpine?

It is small (about 50 MB base) and mysql2, mongodb and redis are pure JavaScript, so no compiler is needed.

Q. Can I use Sequelize or Prisma?

Yes. Run their migrations as a one-off job, like Alembic in project 5.

Q. Should Redis have a volume?

For a pure cache, no. If you lose it, the app reads the database again.

Q. Why not put the password in the Dockerfile?

Anyone who pulls the image could read it with docker history. Use .env or secrets.

Q. Can both stacks run at the same time?

Yes, if you change one host port, for example 3001:3000 in compose.mongo.yaml.

Q. How do I see slow queries?

Use docker compose logs api and add timing logs, or turn on the MySQL slow query log.

Interview questions

  1. How do you connect a container to a database container?

    Model answer: Put both on the same Compose network and use the service name, like db:3306, as the host.

  2. What is cache-aside and when does it go wrong?

    Model answer: The app reads the cache, then the database on a miss. It goes wrong when data changes and the cache is not cleared, so set a TTL and delete keys on writes.

  3. How do you seed a database in Docker?

    Model answer: Mount .sql or .js files into /docker-entrypoint-initdb.d; they run once on an empty volume.

  4. How do you keep one image for many environments?

    Model answer: Read all settings from environment variables; never bake them into the image.

  5. What happens to data when you run docker compose down?

    Model answer: Containers and the network go away. Named volumes stay unless you add -v.

Quick quiz

Q1. What does DB_KIND do in this project?

It chooses which storage file Node loads, so one image works with MySQL or MongoDB.

Q2. When does an init script in docker-entrypoint-initdb.d run?

Only on the first start, when the data volume is empty.

Q3. What is cache-aside?

Read Redis first; on a miss read the database and save the answer in Redis with a TTL.

Q4. Why npm ci and not npm install in the Dockerfile?

npm ci installs exactly the lock file versions and is faster and repeatable.

Q5. How does compose.cache.yaml add Redis without changing the main file?

Compose merges the second -f file on top of the first.

Samajla ka?

Ravi Sir's Cards for this lesson

मराठी cards