Express: bazani ulash
Yakuniy loyihada yozgan ilovada bitta jiddiy
kamchilik bor. Vazifa qo’shasiz, serverni Ctrl + C bilan to’xtatasiz,
qayta ishga tushirasiz — ro’yxat bo’sh. Sabab: tasks massivi server
jarayonining xotirasida yashaydi, jarayon o’lsa massiv ham yo’qoladi.
Bu darsda ma’lumotni faylga — ma’lumotlar bazasiga ko’chiramiz.
Nega aynan SQLite
Node.js 22 dan boshlab SQLite Node ning ichiga qurilgan: node:sqlite
moduli uchun npm install shart emas. Modul API’si
Node.js va SQLite darsida batafsil ko’rilgan —
bu yerda uni Express marshrutlariga ulaymiz.
| Baza | Qachon qulay |
|---|---|
| SQLite | O’rganish, kichik loyiha, bitta server |
| PostgreSQL, MySQL | Ko’p foydalanuvchi, bir nechta server |
Boshlash uchun SQLite yetarli. Marshrutni bazaga ulash usuli PostgreSQL’da ham bir xil bo’lib qoladi — o’zgaradigani ulanish kodi.
Loyihani tayyorlash
task-app loyihasida davom etamiz. Yangi papka yaratamiz:
mkdir db
Tuzilish shunday bo’ladi:
task-app/
├── db/
│ └── index.js
├── routes/
│ └── tasks.js
├── views/
├── public/
├── app.js
└── tasks.db ← baza fayli, kod ishga tushganda o'zi paydo bo'ladi
Ulanish moduli
Baza ulanishi bir joyda yaratiladi va kerakli fayllar uni require
qiladi. db/index.js:
// db/index.js
const path = require("path");
const { DatabaseSync } = require("node:sqlite");
// Baza fayli loyiha ildizida turadi
const databasePath = path.join(__dirname, "..", "tasks.db");
const database = new DatabaseSync(databasePath);
// Jadval bo'lmasa yaratamiz
database.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
is_done INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
)
`);
module.exports = database;
Uch qaror bu yerda qilindi.
path.join(__dirname, "..", "tasks.db") — nisbiy yo’l "tasks.db"
yozilsa, fayl serverni qaysi papkadan ishga tushirganingizga qarab turli
joylarda paydo bo’lardi. __dirname esa har doim db/ papkasini
ko’rsatadi. Bu usul path va papkalar darsida
ko’rilgan.
CREATE TABLE IF NOT EXISTS — server har ishga tushganda bu kod
bajariladi. IF NOT EXISTS bo’lmasa, ikkinchi marta xato chiqadi.
is_done INTEGER — SQLite’da BOOLEAN turi yo’q. true o’rniga 1,
false o’rniga 0 saqlanadi.
Modul to’g’ri ishlayotganini tekshiring:
node -e "require('./db'); console.log('Baza tayyor')"
Baza tayyor
(node:11516) ExperimentalWarning: SQLite is an experimental feature and might change at any time
Papkada tasks.db fayli paydo bo’ldi.
Nomlash: is_done va isDone
Bazada is_done, JSON javobda isDone — nomlar ataylab har xil.
| Joy | Uslub | Sabab |
|---|---|---|
| SQL jadval ustuni | snake_case |
SQL an’anasi, katta-kichik harfga sezgir emas |
| JSON va JavaScript | camelCase |
JS konvensiyasi |
Ikki dunyo o’rtasida tarjimon kerak. Uni bitta kichik funksiya bajaradi:
// Bazadagi qatorni API javobiga aylantiradi
function toTask(row) {
return {
id: row.id,
title: row.title,
isDone: row.is_done === 1,
createdAt: row.created_at,
};
}
row.is_done === 1 muhim: baza 1 yoki 0 beradi, JSON’da esa true
va false kutiladi.
GET — hamma vazifa
Endi CRUD darsidagi marshrutlarni birma-bir
bazaga o’tkazamiz. routes/tasks.js boshi:
const express = require("express");
const database = require("../db");
const router = express.Router();
function toTask(row) {
return {
id: row.id,
title: row.title,
isDone: row.is_done === 1,
createdAt: row.created_at,
};
}
router.get("/", (request, response) => {
const rows = database.prepare("SELECT * FROM tasks ORDER BY id").all();
response.json(rows.map(toTask));
});
module.exports = router;
let tasks = [...] massivi butunlay o’chdi. Uning o’rnini SELECT
so’rovi egalladi. .all() qatorlar massivini qaytaradi, map(toTask)
esa har qatorni JSON shakliga o’tkazadi.
Serverni ishga tushiring:
npm run dev
(node:3088) ExperimentalWarning: SQLite is an experimental feature and might change at any time
Server ishga tushdi: http://localhost:3000
curl http://localhost:3000/api/tasks
[]
Bo’sh massiv — jadval yangi, hali hech narsa qo’shilmagan.
POST — qo’shish va parametrli so’rov
router.post("/", (request, response) => {
const title = request.body.title;
if (typeof title !== "string" || title.trim() === "") {
return response.status(400).json({ error: "title maydoni majburiy" });
}
const result = database
.prepare("INSERT INTO tasks (title) VALUES (?)")
.run(title.trim());
// Yaratilgan qatorni to'liq qaytaramiz
const row = database
.prepare("SELECT * FROM tasks WHERE id = ?")
.get(result.lastInsertRowid);
response.status(201).json(toTask(row));
});
curl -i -X POST http://localhost:3000/api/tasks -H "Content-Type: application/json" -d '{"title":"Bazani ulash"}'
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{"id":1,"title":"Bazani ulash","isDone":false,"createdAt":"2026-09-12 13:58:57"}
Ikki narsa o’zgardi. nextId o’zgaruvchisi kerak emas — id ni
AUTOINCREMENT beradi va run() uni lastInsertRowid maydonida
qaytaradi. createdAt o’zi to’lgan — jadvaldagi DEFAULT (datetime('now')) shuni qiladi.
GET bitta, PUT va DELETE
Qolgan uchta marshrut ham shu qolipda yoziladi:
router.get("/:id", (request, response) => {
const id = Number(request.params.id);
const row = database.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
if (!row) {
return response.status(404).json({ error: "Vazifa topilmadi" });
}
response.json(toTask(row));
});
router.put("/:id", (request, response) => {
const id = Number(request.params.id);
const row = database.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
if (!row) {
return response.status(404).json({ error: "Vazifa topilmadi" });
}
// Holatni teskarisiga o'giramiz
const isDone = row.is_done === 1 ? 0 : 1;
database.prepare("UPDATE tasks SET is_done = ? WHERE id = ?").run(isDone, id);
const updated = database.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
response.json(toTask(updated));
});
router.delete("/:id", (request, response) => {
const id = Number(request.params.id);
const result = database.prepare("DELETE FROM tasks WHERE id = ?").run(id);
// changes — o'chirilgan qatorlar soni
if (result.changes === 0) {
return response.status(404).json({ error: "Vazifa topilmadi" });
}
response.sendStatus(204);
});
curl -X PUT http://localhost:3000/api/tasks/1
{"id":1,"title":"Bazani ulash","isDone":true,"createdAt":"2026-09-12 13:58:57"}
DELETE da alohida SELECT yozilmadi: run() qaytaradigan changes
nechta qator o’chganini aytadi. 0 bo’lsa — bunday id yo’q edi.
Asosiy sinov: serverni o’chirib qayta yoqing
Darsning butun maqsadi shu bitta sinovda.
curl -X POST http://localhost:3000/api/tasks -H "Content-Type: application/json" -d '{"title":"Ikkinchi vazifa"}'
Serverni Ctrl + C bilan to’xtating va qayta ishga tushiring:
npm run dev
curl http://localhost:3000/api/tasks
[{"id":1,"title":"Bazani ulash","isDone":true,"createdAt":"2026-09-12 13:58:57"}]
Ma’lumot joyida. Oldingi darslarda bu ro’yxat bo’sh chiqardi.
Bazani fayl sifatida ko’rish
Kod to’g’ri yozganini tekshirishning eng tez yo’li — bazaga to’g’ridan
qarash. check-db.js faylini yarating:
const database = require("./db");
const rows = database.prepare("SELECT * FROM tasks ORDER BY id").all();
console.log("Jami vazifa:", rows.length);
for (const row of rows) {
console.log(row.id, row.title, row.is_done);
}
node check-db.js
Jami vazifa: 1
1 Bazani ulash 1
Bu yerda is_done qiymati 1 — bazada haqiqatan son saqlanishini
ko’rsatadi. SELECT so’rovi yozuvi select darsidagi
bilan bir xil.
Amaliy mashq
task-app loyihasida davom eting:
db/index.jsni yarating vanode -e "require('./db')"bilantasks.dbfayli paydo bo’lishini tasdiqlang;routes/tasks.jsdagilet tasks = [...]massivini vanextIdni butunlay o’chiring;GET /vaPOST /ni bazaga o’tkazib,curlbilan bitta vazifa qo’shing;toTaskfunksiyasini olib tashlab, javobdais_done: 1chiqishini ko’ring, so’ng qaytaring;PUTvaDELETEni yozib, mavjud bo’lmaganidga404kelishini tekshiring;- Serverni to’xtatib qayta ishga tushiring va
GET /api/tasksro’yxatni saqlaganini ko’ring.
6-qadam butun darsning isboti: shu so’rov bo’sh massiv qaytarsa, ma’lumot hali xotirada, bazada emas.
Qisqacha xulosa
- Xotiradagi massiv server qayta yuklanganda yo’qoladi — doimiy saqlash uchun baza kerak.
node:sqliteNode ichiga qurilgan,npm installshart emas, lekin eksperimental ogohlantirish chiqaradi.- Ulanish
db/index.jsda bir marta yaratiladi;requireuni hamma faylda bitta nusxada beradi. CREATE TABLE IF NOT EXISTSserver har ishga tushganda xavfsiz bajariladi.- Qiymat SQL matniga yopishtirilmaydi —
?orqali alohida uzatiladi, bu SQL injection’ni to’sadi. .all()massiv,.get()bitta qator yokiundefined,.run()esalastInsertRowidvachangesqaytaradi.- Bazadagi
snake_caseustunlartoTaskfunksiyasidacamelCaseJSON maydonlariga aylantiriladi.
Keyingi darsda model qatlami — SQL so’rovlarini marshrutdan ajratib, alohida faylga chiqaramiz.