REST API — qoidalar, best practices, versiyalash

~20 daq · Express · API Arxitekturasi

Muammodan boshlaylik

Jamoada ikki dasturchi. Biri buyurtmalar uchun endpoint yozdi:

POST /getBuyurtma
POST /buyurtmaYaratish
GET  /buyurtma/delete?id=5
POST /updateBuyurtma

Ikkinchisi mahsulotlar uchun:

GET  /mahsulotlarRoyxati
POST /newMahsulot
GET  /mahsulot/5/olib_tashlash
PUT  /mahsulot-update

Frontend dasturchi ikkalasini ishlatishi kerak. Har bir endpoint qanday ishlashini alohida o'qishi, eslab qolishi, tushuntirishi kerak. Hamkasbiga yangi endpoint qo'shganda — "bu qanday ishlaydi?" degan savol.

REST — bu muammoning yechimi. Kelishilgan qoidalar bo'lsa — hamma endpoint qanday ishlashini oldindan biladi.


REST nima?

REST (Representational State Transfer) — HTTP API lari uchun arxitektura uslubi. Qat'iy standart emas, balki kelishilgan qoidalar to'plami.

Asosiy g'oya: resurs + HTTP metod

REST da hamma narsa — resurs (buyurtma, foydalanuvchi, mahsulot). HTTP metodlari resurs ustida amal bajaradi.

Resurs      HTTP Metod   Nima qiladi
─────────────────────────────────────
/users      GET          Barcha foydalanuvchilar
/users      POST         Yangi foydalanuvchi yaratish
/users/5    GET          ID=5 foydalanuvchi
/users/5    PUT          ID=5 ni to'liq yangilash
/users/5    PATCH        ID=5 ni qisman yangilash
/users/5    DELETE       ID=5 ni o'chirish

Bitta URL, oltita harakat — URL da "nima qilasiz" yozilmaydi.


URL qoidalari

1. Ko'plik ism — resurs nomi

✅ /users         ❌ /user
✅ /products      ❌ /getProducts
✅ /orders        ❌ /orderList

Nima uchun ko'plik? Resurs kolleksiyasi. /users — barcha foydalanuvchilar kolleksiyasi. /users/5 — shu kolleksiyadan bittasi.

2. Kichik harf, tire bilan ajratish

✅ /blog-posts        ❌ /blogPosts
✅ /product-reviews   ❌ /ProductReviews
✅ /order-items       ❌ /order_items

3. Iyerarxiya — ichki resurslar

GET  /users/5/orders          ← ID=5 foydalanuvchining buyurtmalari
GET  /users/5/orders/12       ← shu foydalanuvchining 12-buyurtmasi
POST /users/5/orders          ← shu foydalanuvchiga yangi buyurtma
GET  /products/3/reviews      ← 3-mahsulot sharhlari
POST /products/3/reviews      ← 3-mahsulotga yangi sharh

4. Amallar — resurs emas

Ba'zan amal resurs emas — masalan, parol o'zgartirish, to'lov tasdiqlash:

✅ POST /users/5/change-password    ← amal nomi tire bilan
✅ POST /orders/12/confirm
✅ POST /payments/3/refund

❌ PUT  /users/5?action=changePassword
❌ POST /changePassword

HTTP metodlari to'g'ri ishlatish

const router = require('express').Router();

// GET — faqat o'qish, hech narsa o'zgartirmaslik
router.get('/products', async (req, res) => {
  const products = await Product.find();
  res.json(products);
});

router.get('/products/:id', async (req, res) => {
  const product = await Product.findById(req.params.id);
  if (!product) return res.status(404).json({ error: 'Topilmadi' });
  res.json(product);
});

// POST — yangi resurs yaratish
router.post('/products', async (req, res) => {
  const product = await Product.create(req.body);
  res.status(201).json(product);  // ← 201 Created
});

// PUT — to'liq yangilash (barcha maydonlar)
router.put('/products/:id', async (req, res) => {
  const product = await Product.findByIdAndUpdate(
    req.params.id,
    req.body,
    { new: true, overwrite: true }  // ← to'liq almashtirish
  );
  res.json(product);
});

// PATCH — qisman yangilash (faqat yuborilgan maydonlar)
router.patch('/products/:id', async (req, res) => {
  const product = await Product.findByIdAndUpdate(
    req.params.id,
    { $set: req.body },             // ← faqat yuborilgan maydonlar
    { new: true }
  );
  res.json(product);
});

// DELETE — o'chirish
router.delete('/products/:id', async (req, res) => {
  await Product.findByIdAndDelete(req.params.id);
  res.status(204).send();           // ← 204 No Content
});

PUT va PATCH farqi: PUT — to'liq almashtirish (yuborilmagan maydonlar o'chadi). PATCH — faqat yuborilgan maydonlar yangilanadi. Amalda ko'pincha PATCH ishlatiladi.


HTTP status kodlari

// 2xx — Muvaffaqiyat
res.status(200).json(data);        // OK — GET, PUT, PATCH
res.status(201).json(newItem);     // Created — POST
res.status(204).send();            // No Content — DELETE

// 4xx — Mijoz xatosi
res.status(400).json({ error: 'Noto\'g\'ri so\'rov' });    // Bad Request
res.status(401).json({ error: 'Autentifikatsiya kerak' }); // Unauthorized
res.status(403).json({ error: 'Ruxsat yo\'q' });           // Forbidden
res.status(404).json({ error: 'Topilmadi' });              // Not Found
res.status(409).json({ error: 'Konflikt (mavjud)' });      // Conflict
res.status(422).json({ error: 'Validatsiya xatosi' });     // Unprocessable

// 5xx — Server xatosi
res.status(500).json({ error: 'Server xatosi' });          // Internal Server Error
res.status(503).json({ error: 'Xizmat vaqtincha to\'xtatilgan' }); // Service Unavailable

401 vs 403 farqi: 401 — kim ekansiz bilmaymiz (login qiling). 403 — kim ekaningizni bilamiz, lekin ruxsat yo'q.


API versiyalash

Vaqt o'tishi bilan API o'zgaradi. Lekin mavjud mijozlar (mobil ilova, boshqa saytlar) eski API dan foydalanmoqda — ularni bir zumda yangilash imkonsiz.

Versiyalash — eski va yangi API bir vaqtda ishlashi uchun:

// URL da versiya (eng keng tarqalgan)
app.use('/api/v1', require('./routes/v1'));
app.use('/api/v2', require('./routes/v2'));
GET /api/v1/users    ← eski mijozlar ishlaydi
GET /api/v2/users    ← yangi mijozlar yangi formatda

Versiyalar papka tuzilishi

routes/
├── v1/
│   ├── index.js
│   ├── users.js
│   └── products.js
└── v2/
    ├── index.js
    ├── users.js      ← o'zgargan format
    └── products.js
// routes/v1/index.js
const router = require('express').Router();
router.use('/users', require('./users'));
router.use('/products', require('./products'));
module.exports = router;

// routes/v2/index.js — xuddi shunday, lekin v2 modullari
const router = require('express').Router();
router.use('/users', require('./users'));
router.use('/products', require('./products'));
module.exports = router;

Qachon yangi versiya kerak?

Yangi versiya KERAK:
  ✅ Javob formatini o'zgartirish (maydon nomlarini o'zgartirish)
  ✅ Majburiy maydonlarni qo'shish
  ✅ Autentifikatsiya usulini o'zgartirish

Yangi versiya SHART EMAS:
  ✅ Yangi endpoint qo'shish (mavjud endpointlar saqlanadi)
  ✅ Ixtiyoriy maydonlar qo'shish
  ✅ Ichki logikani o'zgartirish (format o'zgarmasa)

To'liq tuzilish misoli

// app.js
const express = require('express');
const app = express();

app.use(express.json());

// Versiyalangan routerlar
app.use('/api/v1', require('./routes/v1'));

// 404 — hech bir route mos kelmasa
app.use((req, res) => {
  res.status(404).json({ error: `${req.method} ${req.path} topilmadi` });
});

// Global xato handler
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: 'Serverda xato yuz berdi' });
});

module.exports = app;
// routes/v1/products.js
const router = require('express').Router();
const Product = require('../../models/Product');
const auth = require('../../middleware/auth');

router.get('/', async (req, res, next) => {
  try {
    const products = await Product.find();
    res.json(products);
  } catch (err) {
    next(err);                              // ← global handler ga yuborish
  }
});

router.post('/', auth, async (req, res, next) => {
  try {
    const product = await Product.create(req.body);
    res.status(201).json(product);
  } catch (err) {
    next(err);
  }
});

module.exports = router;

Tez-tez uchraydigan xatolar

1. GET so'rovda body yuborish

// ❌ — GET body ni qo'llab-quvvatlamaydi (ba'zi mijozlar e'tiborsiz qoldiradi)
app.get('/search', (req, res) => {
  const { query } = req.body; // ← ishlamasligi mumkin
});
// ✅ — GET da query params ishlatiladi
app.get('/search', (req, res) => {
  const { query } = req.query; // ← /search?query=iphone
});

2. DELETE da 200 qaytarish

// ❌ — o'chirilgan narsaning ma'lumotini qaytarish shart emas
res.status(200).json({ message: 'O\'chirildi', data: deletedItem });
// ✅ — 204 No Content — body bo'lmaydi
res.status(204).send();

Sababi: 204 — "muvaffaqiyatli, lekin javob yo'q" degan standart signal. Frontend bu kodni kutadi va body ni o'qimaydi.

3. Xatoda 200 qaytarish

// ❌ — status 200, lekin aslida xato
res.status(200).json({ success: false, error: 'Topilmadi' });
// ✅ — to'g'ri status kodi
res.status(404).json({ error: 'Topilmadi' });

Sababi: mijoz status kodiga qarab qaror qiladi. 200 kelsa — muvaffaqiyat deb hisoblaydi. Body ni har doim o'qimaydi.

4. Versiyasiz API ni o'zgartirish

❌ — /api/users formatini o'zgartirasiz
    Natija: mobil ilova ishlamay qoladi
✅ — /api/v2/users yangi formatda
    /api/v1/users eski format saqlanadi

Xulosa

Qoida Misol
Ko'plik ism /users, /products
HTTP metod = amal GET o'qish, POST yaratish, DELETE o'chirish
To'g'ri status kodi 201 yaratish, 204 o'chirish, 404 topilmadi
Iyerarxiya /users/5/orders
Versiyalash /api/v1/, /api/v2/

REST — kelishuv. Hamma kelishsa — hech kim tushuntirmasdan tushunadi.


Keyingi dars: 37-dars: API Response formatini standartlashtirish