6-bo'lim mashqlari — Node.js + MySQL + Redis
Vazifa
To'liq ishlab chiqish muhitini Compose bilan quramiz. 18-darsdagi kitoblar API ni kengaytiramiz:
- Kitoblar MySQL da saqlansin; jadval va test ma'lumotlari init skript bilan
GET /kitoblarnatijasi Redis da 30 soniya keshlansinPOST /kitoblar— yangi kitob qo'shish, keshni tozalash- API baza sog'lom bo'lgandan keyin ishga tushsin
- Ishlab chiqishda — hot reload (Compose Watch)
- Adminer —
toolsprofilida - Parollar —
.envda, Git dan tashqarida - Production uchun alohida override
Avval o'zingiz yozing. Quyida — yechim.
Tuzilma
kitoblar-api/
├── compose.yaml
├── compose.override.yaml
├── compose.prod.yaml
├── .env.example
├── .env ← Git da emas
├── .dockerignore
├── Dockerfile
├── db/init/
│ └── 01-kitoblar.sql
├── package.json
└── src/
└── index.ts
npm install express mysql2 redis
npm install -D typescript tsx @types/express @types/node
db/init/01-kitoblar.sql
CREATE TABLE kitoblar (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(200) NOT NULL,
muallif VARCHAR(100) NOT NULL,
yil SMALLINT UNSIGNED NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) DEFAULT CHARSET = utf8mb4;
INSERT INTO kitoblar (nom, muallif, yil) VALUES
('O''tkan kunlar', 'Abdulla Qodiriy', 1926),
('Kecha va kunduz', 'Cho''lpon', 1936),
('Sariq devni minib', 'Xudoyberdi To''xtaboyev', 1968);
src/index.ts
import express from 'express';
import mysql from 'mysql2/promise';
import { createClient } from 'redis';
const app = express();
app.use(express.json());
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
connectionLimit: 10,
});
const redis = createClient({ url: process.env.REDIS_URL });
redis.on('error', (e) => console.error('Redis:', e.message));
await redis.connect();
const KESH_KALIT = 'kitoblar:hammasi';
app.get('/health', async (_req, res) => {
try {
await pool.query('SELECT 1');
await redis.ping();
res.json({ holat: 'ok' });
} catch {
res.status(503).json({ holat: 'xato' });
}
});
app.get('/kitoblar', async (_req, res) => {
const kesh = await redis.get(KESH_KALIT);
if (kesh) return res.set('X-Kesh', 'HIT').json(JSON.parse(kesh));
const [rows] = await pool.query('SELECT id, nom, muallif, yil FROM kitoblar ORDER BY id');
await redis.set(KESH_KALIT, JSON.stringify(rows), { EX: 30 });
res.set('X-Kesh', 'MISS').json(rows);
});
app.post('/kitoblar', async (req, res) => {
const { nom, muallif, yil } = req.body ?? {};
if (!nom || !muallif) return res.status(422).json({ xato: 'nom va muallif kerak' });
const [r] = await pool.execute(
'INSERT INTO kitoblar (nom, muallif, yil) VALUES (?, ?, ?)',
[nom, muallif, yil ?? null]
);
await redis.del(KESH_KALIT);
res.status(201).json({ id: (r as mysql.ResultSetHeader).insertId });
});
const PORT = Number(process.env.PORT ?? 3000);
const server = app.listen(PORT, '0.0.0.0', () => console.log(`API ${PORT}-portda`));
process.on('SIGTERM', async () => {
server.close();
await pool.end();
await redis.quit();
process.exit(0);
});
package.json da "type": "module" va tsconfig.json da "module": "nodenext" (top-level await uchun), skriptlar: "dev": "tsx watch src/index.ts", "build": "tsc", "start": "node dist/index.js".
Dockerfile
# syntax=docker/dockerfile:1
FROM node:22-alpine AS base
WORKDIR /app
COPY package.json package-lock.json ./
FROM base AS dev
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]
FROM base AS build
RUN npm ci
COPY . .
RUN npm run build
FROM base AS production
ENV NODE_ENV=production
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
HEALTHCHECK --interval=15s --timeout=3s --start-period=10s \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
compose.yaml — umumiy asos
name: kitoblar
services:
api:
build:
context: .
target: production
restart: unless-stopped
environment:
DB_HOST: baza
DB_NAME: kitoblar
DB_USER: app
DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD kerak}
REDIS_URL: redis://kesh:6379
depends_on:
baza:
condition: service_healthy
kesh:
condition: service_healthy
baza:
image: mysql:8.4
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?DB_ROOT_PASSWORD kerak}
MYSQL_DATABASE: kitoblar
MYSQL_USER: app
MYSQL_PASSWORD: ${DB_PASSWORD}
TZ: Asia/Tashkent
volumes:
- baza-data:/var/lib/mysql
- ./db/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "--silent"]
interval: 5s
timeout: 5s
retries: 20
start_period: 30s
kesh:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 10
adminer:
image: adminer
ports:
- "127.0.0.1:8080:8080"
profiles: [tools]
volumes:
baza-data:
redis-data:
compose.override.yaml — ishlab chiqish
services:
api:
build:
target: dev
ports:
- "3000:3000"
environment:
NODE_ENV: development
develop:
watch:
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: package.json
baza:
ports:
- "127.0.0.1:3306:3306"
compose.prod.yaml va .env.example
# compose.prod.yaml
services:
api:
ports:
- "127.0.0.1:3000:3000"
deploy:
resources:
limits:
memory: 512M
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
# .env.example
DB_ROOT_PASSWORD=
DB_PASSWORD=
Sinov
cp .env.example .env # parollarni to'ldiring
docker compose watch # yoki: docker compose up --watch
Boshqa terminalda:
curl -i localhost:3000/kitoblar # X-Kesh: MISS
curl -i localhost:3000/kitoblar # X-Kesh: HIT
curl -X POST localhost:3000/kitoblar -H "Content-Type: application/json" \
-d '{"nom":"Ufq","muallif":"Said Ahmad","yil":1976}'
curl -i localhost:3000/kitoblar # X-Kesh: MISS — kesh tozalangan, yangi kitob bor
Tekshiring:
-
src/index.tsni o'zgartiring — API rebuild siz qayta yuklanadi -
docker compose down→up— kitoblar saqlangan (volume) -
docker compose --profile tools up -d→ http://localhost:8080 — Adminer (server:baza) -
docker compose ps— hammasi(healthy) -
.envdanDB_PASSWORDni olib tashlang —docker compose upaniq xato bilan to'xtaydi - Production konfiguratsiya:
docker compose -f compose.yaml -f compose.prod.yaml config— dev sozlamalari yo'q
Qo'shimcha vazifalar
- ⭐⭐
migratexizmati —service_completed_successfullybilan (28-dars) - ⭐⭐
workerxizmati — xuddi shu image, boshqacommand(27-dars) - ⭐⭐⭐ Tarmoqlarni ajrating:
bazavakesh—internal: truetarmoqda
O'zingizni tekshiring
- Oddiy
depends_onnima uchun yetmaydi? .envvaenv_filefarqi?compose.override.yamlqachon o'qiladi, qachon yo'q?docker compose downvadown -vfarqi?- Compose Watch ning bind mount dan afzalligi?
profilesnima uchun kerak?
Keyingi bo'lim
7-bo'lim: Real loyihalar — Node.js/NestJS, PHP/Laravel, Python, frontend va reverse proxy.