Node.js — mysql2 moduli, CRUD, parametrli so'rovlar
Muammodan boshlaylik
SQL ni terminalda yozishni o'rgandingiz. Endi uni ilova ichidan ishlatish kerak: Express API mijozlar ro'yxatini qaytarsin, yangi buyurtma qabul qilsin. Node.js dan MySQL ga qanday ulaniladi, so'rov qanday yuboriladi, natija qanday olinadi?
Node.js uchun eng ko'p ishlatiladigan MySQL drayveri — mysql2: tez, Promise / async-await ni qo'llab-quvvatlaydi, haqiqiy prepared statements bor.
O'rnatish
mkdir dokon-api && cd dokon-api
npm init -y
npm install mysql2 express dotenv
package.json ga "type": "module" qo'shing — import sintaksisi uchun.
.env (Git ga qo'shilmaydi):
DB_HOST=localhost
DB_PORT=3306
DB_USER=dokon_app
DB_PASSWORD=Kuchli_Parol_2026!
DB_NAME=dokon
Ulanish — pool bilan
db.js:
import mysql from 'mysql2/promise';
import 'dotenv/config';
export const pool = mysql.createPool({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
connectionLimit: 10,
charset: 'utf8mb4',
timezone: 'Z', // sanalarni UTC sifatida
decimalNumbers: true, // DECIMAL → JS number (string emas)
});
Har so'rovda yangi ulanish emas — pool (54-dars). mysql2/promise — async/await bilan ishlash uchun.
Ulanishni tekshirish:
const [rows] = await pool.query('SELECT VERSION() AS v');
console.log('MySQL', rows[0].v);
Natija tuzilishi
const [rows, fields] = await pool.query('SELECT id, nom, narx FROM mahsulotlar LIMIT 2');
console.log(rows);
// [
// { id: 1, nom: 'iPhone 15', narx: 12500000 },
// { id: 2, nom: 'Samsung Galaxy A55', narx: 4800000 }
// ]
So'rov massiv qaytaradi: [natija, ustunlar_haqida_ma'lumot]. Odatda faqat birinchisi kerak — shuning uchun const [rows] = ... destructuring.
SELECT uchun rows — ob'ektlar massivi. INSERT/UPDATE/DELETE uchun esa — natija ob'ekti:
const [result] = await pool.query("UPDATE mahsulotlar SET soni = soni + 1 WHERE id = 1");
console.log(result.affectedRows); // 1
console.log(result.changedRows); // 1
Parametrli so'rovlar — majburiy
// ❌ SQL INJECTION (51-dars)
const [rows] = await pool.query(`SELECT * FROM mijozlar WHERE email = '${email}'`);
// ✅ Placeholder
const [rows] = await pool.execute('SELECT * FROM mijozlar WHERE email = ?', [email]);
execute va query farqi
pool.execute(sql, params) |
pool.query(sql, params) |
|
|---|---|---|
| Qanday | Haqiqiy prepared statement — server tomonida | Drayver qiymatlarni xavfsiz qochirib, SQL ga qo'shadi |
| Xavfsizlik | ✅ | ✅ (placeholder ishlatilsa) |
| Kesh | Statement keshlanadi — takroriy so'rovlar tezroq | Yo'q |
IN (?) massiv bilan |
❌ | ✅ massivni 1, 2, 3 ga yoyadi |
Odatda — execute. IN ro'yxati yoki dinamik qismlar kerak bo'lsa — query.
const ids = [1, 3, 5];
const [rows] = await pool.query('SELECT id, nom FROM mahsulotlar WHERE id IN (?)', [ids]);
// SELECT ... WHERE id IN (1, 3, 5)
CRUD — to'liq misol
mahsulotlar.js:
import { pool } from './db.js';
// READ — ro'yxat, filtr va sahifalash bilan
export async function royxat({ toifaId, sahifa = 1, hajm = 20 }) {
const shartlar = [];
const params = [];
if (toifaId) {
shartlar.push('toifa_id = ?');
params.push(toifaId);
}
const where = shartlar.length ? `WHERE ${shartlar.join(' AND ')}` : '';
const offset = (sahifa - 1) * hajm;
const [rows] = await pool.query(
`SELECT id, nom, narx, soni FROM mahsulotlar ${where} ORDER BY id LIMIT ? OFFSET ?`,
[...params, hajm, offset]
);
return rows;
}
// READ — bitta
export async function top(id) {
const [rows] = await pool.execute(
'SELECT id, toifa_id, nom, narx, soni FROM mahsulotlar WHERE id = ?',
[id]
);
return rows[0] ?? null;
}
// CREATE
export async function yarat({ toifaId, nom, narx, soni = 0 }) {
const [result] = await pool.execute(
'INSERT INTO mahsulotlar (toifa_id, nom, narx, soni) VALUES (?, ?, ?, ?)',
[toifaId ?? null, nom, narx, soni]
);
return result.insertId;
}
// UPDATE
export async function yangila(id, { nom, narx }) {
const [result] = await pool.execute(
'UPDATE mahsulotlar SET nom = ?, narx = ? WHERE id = ?',
[nom, narx, id]
);
return result.affectedRows > 0;
}
// DELETE
export async function ochir(id) {
const [result] = await pool.execute('DELETE FROM mahsulotlar WHERE id = ?', [id]);
return result.affectedRows > 0;
}
E'tibor bering: WHERE dinamik yasalyapti, lekin SQL ga faqat bizning matnimiz ('toifa_id = ?') qo'shiladi. Foydalanuvchi qiymatlari — faqat params orqali.
[!WARNING]
executebilanundefinedqiymat berib bo'lmaydi:Bind parameters must not contain undefined. Ixtiyoriy maydonlarni?? nullbilannullga aylantiring.
Express API
server.js:
import express from 'express';
import * as Mahsulot from './mahsulotlar.js';
const app = express();
app.use(express.json());
app.get('/api/mahsulotlar', async (req, res, next) => {
try {
const rows = await Mahsulot.royxat({
toifaId: req.query.toifa ? Number(req.query.toifa) : undefined,
sahifa: Number(req.query.sahifa) || 1,
});
res.json({ data: rows });
} catch (e) { next(e); }
});
app.get('/api/mahsulotlar/:id', async (req, res, next) => {
try {
const m = await Mahsulot.top(Number(req.params.id));
if (!m) return res.status(404).json({ xato: 'Topilmadi' });
res.json({ data: m });
} catch (e) { next(e); }
});
app.post('/api/mahsulotlar', async (req, res, next) => {
try {
const { nom, narx } = req.body;
if (!nom || !Number.isInteger(narx) || narx <= 0) {
return res.status(422).json({ xato: "nom va musbat butun narx kerak" });
}
const id = await Mahsulot.yarat(req.body);
res.status(201).json({ data: await Mahsulot.top(id) });
} catch (e) { next(e); }
});
app.delete('/api/mahsulotlar/:id', async (req, res, next) => {
try {
const ok = await Mahsulot.ochir(Number(req.params.id));
res.status(ok ? 204 : 404).end();
} catch (e) { next(e); }
});
// Xatolar
app.use((err, req, res, next) => {
console.error(err);
if (err.code === 'ER_DUP_ENTRY') return res.status(409).json({ xato: 'Takroriy qiymat' });
if (err.code === 'ER_NO_REFERENCED_ROW_2') return res.status(422).json({ xato: "Bog'liq yozuv topilmadi" });
if (err.code === 'ER_ROW_IS_REFERENCED_2') return res.status(409).json({ xato: "Bu yozuv boshqa joyda ishlatilgan" });
res.status(500).json({ xato: 'Ichki xato' });
});
app.listen(3000, () => console.log('http://localhost:3000'));
MySQL xato kodlari
err.code va err.errno orqali xatoni aniq ushlash:
err.code |
errno |
Ma'nosi |
|---|---|---|
ER_DUP_ENTRY |
1062 | UNIQUE buzildi |
ER_NO_REFERENCED_ROW_2 |
1452 | Foreign key — ota yo'q |
ER_ROW_IS_REFERENCED_2 |
1451 | Foreign key — bolasi bor otani o'chirish |
ER_CHECK_CONSTRAINT_VIOLATED |
3819 | CHECK buzildi |
ER_LOCK_DEADLOCK |
1213 | Deadlock (38-dars) |
ECONNREFUSED |
— | MySQL ishlamayapti |
Xom MySQL xato matnini foydalanuvchiga ko'rsatmang — logga yozing.
Tur moslashuvi
| MySQL | JavaScript |
|---|---|
INT, SMALLINT |
number |
BIGINT |
number (katta qiymatlarda aniqlik yo'qoladi!) yoki supportBigNumbers: true, bigNumberStrings: true bilan string |
DECIMAL |
string (standart) yoki decimalNumbers: true bilan number |
DATETIME, TIMESTAMP |
Date |
TINYINT(1) |
number (0/1), boolean emas |
JSON |
Ob'ekt (avtomatik parse) |
Pul uchun DECIMAL — string sifatida olish va maxsus kutubxona (decimal.js) bilan hisoblash xavfsizroq.
Nima uchun shunday?
Nima uchun mysql2 natijani [rows, fields] massivi sifatida qaytaradi — faqat rows qaytarsa qulayroq emasmi?
Chunki bitta interfeys turli so'rovlarga xizmat qiladi: SELECT — qatorlar, INSERT — natija ob'ekti (insertId, affectedRows), ko'p so'rovli chaqiruv yoki procedure (CALL) — bir nechta natija to'plami. fields esa ustun turlari va nomlari haqida metama'lumot — ORM lar va universal vositalar uchun. Massiv destructuring bilan kerakli qismni olish oson: const [rows] = ....
Xulosa
Eslab qoling:
mysql2/promise+createPool— ulanishconst [rows] = await pool.execute(sql, params)— natija- Har doim placeholder
?; template string bilan SQL yasamang execute— prepared statement;query—IN (?)va dinamik qismlar uchunresult.insertId,result.affectedRows— yozish natijalariundefined→nullerr.codebo'yicha xatolarni HTTP status ga aylantiringBIGINT,DECIMAL,TINYINT(1)turlariga e'tibor
Keyingi dars
56-dars: Node.js — Transaction, xatolarni boshqarish