Express model qatlami va kod tuzilishi

Oldingi darsda marshrutlar bazaga ulandi va kod ishladi. Lekin routes/tasks.js ni qayta o’qib chiqing: har marshrut ichida database.prepare(...) va SQL matni turibdi. Beshta marshrutda SELECT * FROM tasks WHERE id = ? uch marta takrorlangan.

Ustun nomi o’zgarsa, uni beshta joyda tuzatishingiz kerak bo’ladi.

Marshrutning ishi nima

Marshrut funksiyasi hozir uchta ish qilyapti:

  1. So’rovdan ma’lumot oladi (request.body, request.params);
  2. SQL yozadi va bazaga boradi;
  3. Javob va holat kodini qaytaradi.

Birinchi va uchinchisi — HTTP ishi. Ikkinchisi — ma’lumot ishi. Ular bir-biriga aloqasi yo’q ikki mavzu, lekin bitta funksiyada chalkashib yotibdi.

Ajratish nima beradi

Uchta aniq foyda bor.

Muammo Ajratilmagan kodda Model qatlami bilan
SQL takrorlanadi Beshta marshrutda Bitta funksiyada
Test yozish Server ishga tushirish kerak Funksiyani to’g’ridan chaqirasiz
Bazani almashtirish Har marshrut qayta yoziladi Faqat model o’zgaradi

Uchinchi band uzoq muddatli. SQLite’dan PostgreSQL’ga o’tsangiz, models/task.js ichini qayta yozasiz — routes/tasks.js ga esa qo’l ham tegmaydi, chunki u Task.getAll() ni chaqiradi va natijada massiv kutadi. Bu shart o’zgarmaydi.

Papka tuzilishi

task-app/
├── db/
│   └── index.js       ← ulanish
├── models/
│   └── task.js        ← SQL shu yerda
├── routes/
│   └── tasks.js       ← faqat HTTP
├── views/
├── public/
└── app.js

Qoidani bitta jumlaga sig’diring: routes/ ichida SELECT so’zi uchramaydi, models/ ichida response so’zi uchramaydi.

models/task.js

// models/task.js
const database = require("../db");

// Bazadagi qatorni ilova obyektiga aylantiradi
function toTask(row) {
  return {
    id: row.id,
    title: row.title,
    isDone: row.is_done === 1,
    createdAt: row.created_at,
  };
}

function getAll() {
  const rows = database.prepare("SELECT * FROM tasks ORDER BY id").all();
  return rows.map(toTask);
}

function getById(id) {
  const row = database.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
  return row ? toTask(row) : null;
}

function create(title) {
  const result = database
    .prepare("INSERT INTO tasks (title) VALUES (?)")
    .run(title);

  return getById(result.lastInsertRowid);
}

function update(id, changes) {
  const task = getById(id);

  if (!task) {
    return null;
  }

  // Yuborilmagan maydon eski qiymatida qoladi
  const title = changes.title === undefined ? task.title : changes.title;
  const isDone = changes.isDone === undefined ? task.isDone : changes.isDone;

  database
    .prepare("UPDATE tasks SET title = ?, is_done = ? WHERE id = ?")
    .run(title, isDone ? 1 : 0, id);

  return getById(id);
}

function remove(id) {
  const result = database.prepare("DELETE FROM tasks WHERE id = ?").run(id);
  return result.changes > 0;
}

module.exports = { getAll, getById, create, update, remove };

Beshta funksiya — CRUD ning to’rt amali va getById. Nomlar HTTP usullari bilan atalmagani muhim: getAll deyiladi, handleGet emas. Model HTTP haqida hech narsa bilmaydi.

create va update oxirida getById chaqirilishiga e’tibor bering. Nega: baza created_at ni o’zi to’ldiradi, id ni o’zi beradi. Faqat INSERT qilib qo’ysangiz, bu qiymatlarni bilmaysiz. Qayta o’qish — haqiqiy holatni qaytarishning ishonchli yo’li.

Qaytariladigan qiymat shartnomasi

Model qatlamining eng muhim qismi — nima qaytarishi. Marshrut shunga tayanadi.

Funksiya Topilganda Topilmaganda
getAll() Obyektlar massivi Bo’sh massiv []
getById(id) Obyekt null
create(title) Yaratilgan obyekt
update(id, changes) Yangilangan obyekt null
remove(id) true false

routes/tasks.js — endi faqat HTTP

const express = require("express");
const Task = require("../models/task");

const router = express.Router();

router.get("/", (request, response) => {
  response.json(Task.getAll());
});

router.get("/:id", (request, response) => {
  const task = Task.getById(Number(request.params.id));

  if (!task) {
    return response.status(404).json({ error: "Vazifa topilmadi" });
  }

  response.json(task);
});

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 task = Task.create(title.trim());
  response.status(201).json(task);
});

router.put("/:id", (request, response) => {
  const task = Task.update(Number(request.params.id), request.body);

  if (!task) {
    return response.status(404).json({ error: "Vazifa topilmadi" });
  }

  response.json(task);
});

router.delete("/:id", (request, response) => {
  const isRemoved = Task.remove(Number(request.params.id));

  if (!isRemoved) {
    return response.status(404).json({ error: "Vazifa topilmadi" });
  }

  response.sendStatus(204);
});

module.exports = router;

Fayl deyarli ikki barobar qisqardi. Har marshrut endi bir qarashda o’qiladi: ma’lumotni ol, yo’q bo’lsa 404, bor bo’lsa qaytar.

Task nomi bosh harf bilan yozilgani — konvensiya: modul butun bir mavzuni ifodalaydi. const Task = require("../models/task") yozuvi Task.getAll() chaqiruvini o’qishli qiladi.

curl -X POST http://localhost:3000/api/tasks -H "Content-Type: application/json" -d '{"title":"Model qatlami"}'
{"id":4,"title":"Model qatlami","isDone":false,"createdAt":"2026-09-12 14:01:41"}
curl -X PUT http://localhost:3000/api/tasks/4 -H "Content-Type: application/json" -d '{"isDone":true}'
{"id":4,"title":"Model qatlami","isDone":true,"createdAt":"2026-09-12 14:01:41"}

API tashqaridan butunlay o’zgarmadi. Bu yaxshi belgi: refaktoring ichki tuzilishni o’zgartiradi, xulq-atvorni emas.

Modelni serversiz sinash

Ajratishning eng ko’rinarli foydasi shu yerda. model-check.js:

const Task = require("./models/task");

console.log("Bazadagi vazifalar:", Task.getAll().length);

const task = Task.create("Test ichidan");
console.log("Yaratildi:", task);

console.log("O'chirildi:", Task.remove(task.id));
console.log("Yo'q id:", Task.getById(99999));
node model-check.js
Bazadagi vazifalar: 3
Yaratildi: {
  id: 5,
  title: 'Test ichidan',
  isDone: false,
  createdAt: '2026-09-12 14:01:41'
}
O'chirildi: true
Yo'q id: null

Server ishga tushirilmadi, curl yozilmadi, port band qilinmadi. Shunchaki funksiya chaqirildi. Testlash darsida bu yondashuv avtomatik testlarga aylanadi.

Xato qayerda ushlanadi

Yangi savol tug’iladi: baza xato bersa (fayl o’chirilgan, disk to’lgan), uni kim ushlaydi?

Javob: model ushlamaydi. U xatoni yuqoriga qo’yib yuboradi, marshrut esa next(error) bilan xato middleware’iga uzatadi.

router.get("/", (request, response, next) => {
  try {
    response.json(Task.getAll());
  } catch (error) {
    next(error);
  }
});

Sabab: model javob qaytara olmaydi — uning response obyekti yo’q. Xatoni yutib yuborsa (catch ichida console.log qilib qo’ysa), marshrut hech narsa bo’lmagandek davom etadi va mijoz noto’g’ri javob oladi.

Qatlamlar qanchagacha ko’payadi

Katta loyihalarda oraliqda yana bir qatlam paydo bo’ladi.

Qatlam Vazifasi Misol
Route (controller) HTTP so’rov va javob routes/tasks.js
Service Biznes qoidalari “Bajarilgan vazifani tahrirlab bo’lmaydi”
Model (repository) Baza bilan muloqot models/task.js

Amaliy mashq

task-app loyihasida davom eting:

  1. models/ papkasini yaratib, task.js ichiga getAll va getById funksiyalarini ko’chiring;
  2. routes/tasks.js dagi GET marshrutlarini model chaqiruviga almashtiring va curl bilan javob o’zgarmaganini tasdiqlang;
  3. create, update, remove ni ham ko’chirib, routes/tasks.js da database so’zi qolmaganini tekshiring;
  4. model-check.js yozib, serverni ishga tushirmasdan Task.create va Task.remove ni sinang;
  5. getById da null o’rniga undefined qaytaring va marshrut hamon 404 berishini kuzating, so’ng null ga qaytaring;
  6. update ga faqat { title: "Yangi nom" } yuboring — isDone eski qiymatida qolishi kerak.

4-qadam ajratishning asosiy foydasini ko’rsatadi: model qatlami HTTP siz ham to’liq ishlaydi.

Qisqacha xulosa

  • Marshrut ichidagi SQL takrorlanadi va o’zgartirishni qiyinlashtiradi.
  • routes/ faqat HTTP bilan, models/ faqat ma’lumot bilan ishlaydi.
  • Model funksiyalari HTTP tushunchalarini bilmaydi: getAll, create, remove deb ataladi.
  • Qaytariladigan qiymat shartnomasi aniq bo’lsin: topilmasa null, o’chirilsa true.
  • create va update oxirida yozuvni qayta o’qish id va created_at kabi baza to’ldirgan maydonlarni qaytaradi.
  • Model xatoni ushlamaydi — marshrut uni next(error) bilan uzatadi.
  • Ajratilgan modelni server ishga tushirmasdan sinash mumkin — bu testlashning asosi.

Keyingi darsda migratsiya va seed — jadval tuzilishini qo’lda emas, kod bilan boshqarish.

Bu dars foydali bo'ldimi?

marta ko'rildi