Express yakuniy loyiha: API va frontend

O’n dars davomida har tushunchani alohida ko’rdik: marshrut, middleware, statik fayl, xato ishlovchisi, CRUD API va shablon. Endi ularni bitta ishlaydigan ilovaga yig’amiz — server ham sahifani beradi, ham API bilan xizmat qiladi, frontend esa fetch orqali u bilan gaplashadi.

Bo’limni jamlash

Loyihaga kirishdan oldin o’tilganni bir ko’z bilan ko’rib chiqamiz. Har qatordagi havola — o’sha tushuncha batafsil yozilgan dars.

Tushuncha Asosiy vosita Dars
Framework g’oyasi express() Express nima
Server ishga tushirish app.listen() Birinchi server
Manzillar app.get(), :id, Router Marshrutlash
Ma’lumot olish va berish request.body, response.json() So’rov va javob
Oraliq funksiyalar app.use(), next() Middleware
Fayl berish express.static() Statik fayllar
Xato va 404 To’rt parametrli middleware Xatolarni boshqarish
To’rt amal GET, POST, PUT, DELETE CRUD API
Ma’lumot tekshiruvi Server tomonda validatsiya Forma va validatsiya
HTML sahifa response.render() EJS shabloni

Loyiha nima qiladi

Vazifalar ilovasi. Foydalanuvchi brauzerda ro’yxatni ko’radi, yangi vazifa qo’shadi, bajarilgan deb belgilaydi va o’chiradi. Sahifa yangilanmaydi — hamma amal fetch bilan bajariladi.

Loyiha tuzilishi

task-app/
├── public/
│   ├── style.css
│   └── app.js
├── routes/
│   └── tasks.js
├── views/
│   ├── partials/
│   │   ├── header.ejs
│   │   └── footer.ejs
│   └── index.ejs
├── app.js
└── package.json

Har papkaning bitta vazifasi bor: public — brauzerga beriladigan fayllar, routes — API marshrutlari, views — HTML shablonlar. Ildizdagi app.js hammasini bog’laydi.

Boshlash:

mkdir task-app
cd task-app
npm init -y
npm install express ejs
npm install --save-dev nodemon

package.json ga ishga tushirish buyrug’ini qo’shing:

{
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js"
  }
}

1-qadam: asosiy fayl

app.js:

const express = require("express");
const path = require("path");
const tasksRouter = require("./routes/tasks");

const app = express();
const PORT = 3000;

// Shablon sozlamalari
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));

// Kirish middleware'lari — marshrutlardan yuqorida
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, "public")));

// Bosh sahifa — HTML qaytaradi
app.get("/", (request, response) => {
  response.render("index", { pageTitle: "Vazifalar ilovasi" });
});

// API — JSON qaytaradi
app.use("/api/tasks", tasksRouter);

// Topilmagan manzil
app.use((request, response) => {
  response.status(404).json({ error: "Bunday manzil topilmadi" });
});

// Xato ishlovchisi — to'rt parametr
app.use((err, request, response, next) => {
  // Tafsilot faqat terminalga
  console.error(err);
  response.status(500).json({ error: "Serverda xato yuz berdi" });
});

app.listen(PORT, () => {
  console.log(`Server ishga tushdi: http://localhost:${PORT}`);
});

Tartibga e’tibor bering: middleware → marshrutlar → 404 → xato ishlovchisi. Xatolarni boshqarish darsida ko’rganimizdek, bu tartib o’zgarsa tizim buziladi.

2-qadam: API marshrutlari

routes/tasks.jsCRUD API darsidagi kod, ustiga validatsiya darsidagi tekshiruv qo’shilgan:

const express = require("express");

const router = express.Router();

let tasks = [
  { id: 1, title: "Express o'rganish", isDone: true },
  { id: 2, title: "Yakuniy loyihani yozish", isDone: false },
];

let nextId = 3;

// Sarlavhani tekshiradigan middleware
function checkTitle(request, response, next) {
  const title = request.body.title;

  if (typeof title !== "string" || title.trim() === "") {
    return response.status(400).json({
      errors: [{ field: "title", message: "Sarlavha bo'sh bo'lmasin" }],
    });
  }

  if (title.trim().length > 100) {
    return response.status(400).json({
      errors: [{ field: "title", message: "Sarlavha 100 belgidan oshmasin" }],
    });
  }

  next();
}

// Hamma vazifa
router.get("/", (request, response) => {
  response.json(tasks);
});

// Yangi vazifa
router.post("/", checkTitle, (request, response) => {
  const task = {
    id: nextId,
    title: request.body.title.trim(),
    isDone: false,
  };

  nextId = nextId + 1;
  tasks.push(task);

  response.status(201).json(task);
});

// Holatni almashtirish
router.put("/:id", (request, response) => {
  const id = Number(request.params.id);
  const task = tasks.find((item) => item.id === id);

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

  task.isDone = !task.isDone;
  response.json(task);
});

// O'chirish
router.delete("/:id", (request, response) => {
  const id = Number(request.params.id);
  const exists = tasks.some((item) => item.id === id);

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

  tasks = tasks.filter((item) => item.id !== id);
  response.sendStatus(204);
});

module.exports = router;

Serverni ishga tushirib, API ni alohida tekshiring:

npm run dev
curl http://localhost:3000/api/tasks
[{"id":1,"title":"Express o'rganish","isDone":true},{"id":2,"title":"Yakuniy loyihani yozish","isDone":false}]

API ishlayotganiga ishonch hosil qilgandan keyingina frontendga o’ting. Ikkisini bir vaqtda yozsangiz, xato qayerdaligini aniqlash qiyinlashadi.

3-qadam: shablonlar

views/partials/header.ejs:

<!DOCTYPE html>
<html lang="uz">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= pageTitle %></title>
    <link rel="stylesheet" href="/style.css" />
  </head>
  <body>
    <main>

views/partials/footer.ejs:

    </main>
    <script src="/app.js"></script>
  </body>
</html>

views/index.ejs:

<%- include("partials/header") %>

<h1><%= pageTitle %></h1>

<form id="task-form">
  <input
    type="text"
    id="task-input"
    placeholder="Yangi vazifa"
    maxlength="100"
  />
  <button type="submit">Qo'shish</button>
</form>

<p id="message"></p>

<ul id="task-list"></ul>

<%- include("partials/footer") %>

Ro’yxat bo’sh: uni brauzerdagi JavaScript to’ldiradi. <script> tegi footer’da, sahifa oxirida turibdi — shunda skript ishga tushganda elementlar allaqachon mavjud bo’ladi.

4-qadam: frontend

public/app.js — brauzerda ishlaydigan kod:

const form = document.getElementById("task-form");
const input = document.getElementById("task-input");
const list = document.getElementById("task-list");
const message = document.getElementById("message");

// Serverdan ro'yxatni olib, sahifaga chizamiz
async function loadTasks() {
  try {
    const response = await fetch("/api/tasks");

    if (!response.ok) {
      throw new Error("Ro'yxat yuklanmadi");
    }

    const tasks = await response.json();
    renderTasks(tasks);
  } catch (error) {
    message.textContent = error.message;
  }
}

function renderTasks(tasks) {
  // Eski ro'yxatni tozalaymiz
  list.textContent = "";

  for (const task of tasks) {
    const item = document.createElement("li");

    const titleSpan = document.createElement("span");
    // textContent matnni matn sifatida qo'yadi — teg bajarilmaydi
    titleSpan.textContent = task.title;

    if (task.isDone) {
      titleSpan.style.textDecoration = "line-through";
    }

    const toggleButton = document.createElement("button");
    toggleButton.textContent = task.isDone ? "Qaytarish" : "Bajarildi";
    toggleButton.addEventListener("click", () => toggleTask(task.id));

    const deleteButton = document.createElement("button");
    deleteButton.textContent = "O'chirish";
    deleteButton.addEventListener("click", () => deleteTask(task.id));

    item.append(titleSpan, toggleButton, deleteButton);
    list.append(item);
  }
}

form.addEventListener("submit", async (e) => {
  e.preventDefault();
  message.textContent = "";

  try {
    const response = await fetch("/api/tasks", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ title: input.value }),
    });

    const data = await response.json();

    if (!response.ok) {
      // Server qaytargan tekshiruv xatosini ko'rsatamiz
      message.textContent = data.errors[0].message;
      return;
    }

    input.value = "";
    loadTasks();
  } catch (error) {
    message.textContent = "Server bilan bog'lanib bo'lmadi";
  }
});

async function toggleTask(id) {
  await fetch(`/api/tasks/${id}`, { method: "PUT" });
  loadTasks();
}

async function deleteTask(id) {
  await fetch(`/api/tasks/${id}`, { method: "DELETE" });
  loadTasks();
}

loadTasks();

fetch va async/await fetch darsida va async/await darsida ko’rilgan. Yangi jihat bitta: so’rov endi o’zingiz yozgan serverga ketyapti.

5-qadam: stillar

public/style.css:

body {
  font-family: system-ui, sans-serif;
  background: #f9fafb;
  margin: 0;
  padding: 24px;
}

main {
  max-width: 560px;
  margin: 0 auto;
  background: #ffffff;
  padding: 24px;
  border-radius: 12px;
}

h1 {
  margin-top: 0;
  color: #1f2937;
}

#task-form {
  display: flex;
  gap: 8px;
}

#task-input {
  flex: 1;
  padding: 8px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
}

#message {
  color: #b91c1c;
  min-height: 20px;
}

#task-list {
  list-style: none;
  padding: 0;
}

#task-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid #e5e7eb;
}

#task-list span {
  flex: 1;
}

Ishlayotganini tekshirish

Brauzerda http://localhost:3000 ni oching va tartib bilan sinang:

  1. Ro’yxatda ikkita boshlang’ich vazifa ko’rinadimi;
  2. Yangi vazifa qo’shilib, ro’yxatda darhol paydo bo’ladimi;
  3. Bajarildi tugmasi matnni chizib tashlaydimi;
  4. O’chirish vazifani yo’q qiladimi;
  5. Bo’sh maydon bilan Qo’shish bosilsa, qizil xato xabari chiqadimi;
  6. Sahifa yangilanganda ro’yxat saqlanib qoladimi.

Oxirgi band eng muhimi: ma’lumot serverda turibdi, shuning uchun sahifani yangilash uni yo’qotmaydi.

Loyihani baholash mezoni

O’z ishingizni shu ro’yxat bo’yicha tekshiring:

Mezon Qanday tekshiriladi
To’rt amal ishlaydi Qo’shish, ko’rish, belgilash, o’chirish
To’g’ri holat kodlari curl -i da 201, 204, 400, 404
Server tekshiruvi bor curl bilan bo’sh title yuborib ko’ring
Xato foydalanuvchiga ko’rinadi Bo’sh maydonda qizil xabar chiqadi
Tafsilot yashiringan 500 javobida stack yo’q
Papkalar ajratilgan routes, views, public alohida
Frontend matnni xavfsiz qo’yadi innerHTML ishlatilmagan

Oxirgi ikki band ko’pincha e’tibordan chetda qoladi, lekin haqiqiy loyihada eng ko’p muammo tug’diradigan joy aynan shular.

Bilimni tekshirish

Kodga qaramay javob berishga urinib ko’ring:

  1. app.use(express.json()) ni marshrutlardan pastga ko’chirsangiz nima buziladi?
  2. next() chaqirilmagan middleware qanday alomat beradi?
  3. Nega id ni mijoz emas, server beradi?
  4. <%= %> va <%- %> orasidagi farq qaysi hujum turiga bog’liq?
  5. Xato middleware’ini Express qaysi belgisiga qarab taniydi?
  6. DELETE javobida nega 204 qaytariladi, 200 emas?
  7. public papkasiga .env faylini qo’ysangiz nima bo’ladi?

Javobi darhol esga kelmagan savol bo’lsa, yuqoridagi jadvaldan tegishli darsga qayting — bu bilimdagi bo’shliqni topishning eng tez yo’li.

Loyihani kengaytirish

Ilova ishlagach, o’zingizni sinash uchun qo’shimcha vazifalar:

  • Vazifani tahrirlash imkonini qo’shing (PUT bilan title yangilash);
  • Bajarilganlarni yashiradigan filtr qiling (?isDone=false bilan);
  • Har vazifaga muhimlik darajasi va saralash qo’shing;
  • morgan ni ulab, so’rovlarni terminalda kuzating;
  • /about sahifasini include ishlatib yarating.

Har biri o’tilgan darslardagi vositalar bilan bajariladi — yangi tushuncha kerak emas.

Nima o’rgandingiz va endi nima

Bu bo’limda sof http moduli bilan qo’lda yozilgan serverdan boshlab, papkalarga bo’lingan, xatolarni boshqaradigan va frontend bilan gaplashadigan ilovagacha yetdingiz. Marshrut, middleware, request va response, statik fayl, shablon va CRUD — Node.js frameworklarining deyarli hammasida shu tushunchalar bir xil ataladi.

Loyihada ataylab qoldirilgan uchta bo’shliq bor va ularning har biri keyingi qadam:

Ma’lumotlar bazasi. tasks massivi server qayta yuklanganda tozalanadi. Haqiqiy loyihada ma’lumot bazada saqlanadi — bu mavzu bilan ma’lumotlar bazasi darsida tanishib chiqing, so’ng SQL bo’limiga o’ting.

Autentifikatsiya. Hozir ilovadagi vazifalar hamma uchun umumiy. Foydalanuvchini tanib, faqat o’z ro’yxatini ko’rsatish uchun kirish tizimi kerak: cookie, sessiya va autentifikatsiya darsi.

Deploy. Ilova hozir faqat localhost da ishlaydi. Uni internetga chiqarish alohida ish: deploy va operatsiyalar darsida qaysi qadamlar kerakligi yozilgan.

Ilovani git bilan saqlab qo’ying va node_modules/ ni .gitignore ga kiritishni unutmang — u haqda npm va package.json darsida gapirilgan edi. Bu loyiha portfelingizdagi birinchi backend ishi bo’lishi mumkin.

Bu dars foydali bo'ldimi?

marta ko'rildi