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 emasid 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:

  1. db/index.js ni yarating va node -e "require('./db')" bilan tasks.db fayli paydo bo’lishini tasdiqlang;
  2. routes/tasks.js dagi let tasks = [...] massivini va nextId ni butunlay o’chiring;
  3. GET / va POST / ni bazaga o’tkazib, curl bilan bitta vazifa qo’shing;
  4. toTask funksiyasini olib tashlab, javobda is_done: 1 chiqishini ko’ring, so’ng qaytaring;
  5. PUT va DELETE ni yozib, mavjud bo’lmagan id ga 404 kelishini tekshiring;
  6. Serverni to’xtatib qayta ishga tushiring va GET /api/tasks ro’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:sqlite Node ichiga qurilgan, npm install shart emas, lekin eksperimental ogohlantirish chiqaradi.
  • Ulanish db/index.js da bir marta yaratiladi; require uni hamma faylda bitta nusxada beradi.
  • CREATE TABLE IF NOT EXISTS server har ishga tushganda xavfsiz bajariladi.
  • Qiymat SQL matniga yopishtirilmaydi — ? orqali alohida uzatiladi, bu SQL injection’ni to’sadi.
  • .all() massiv, .get() bitta qator yoki undefined, .run() esa lastInsertRowid va changes qaytaradi.
  • Bazadagi snake_case ustunlar toTask funksiyasida camelCase JSON maydonlariga aylantiriladi.

Keyingi darsda model qatlami — SQL so’rovlarini marshrutdan ajratib, alohida faylga chiqaramiz.

Bu dars foydali bo'ldimi?

marta ko'rildi