15-bo‘lim

REST API qurish

Resurslar va HTTP metodlari, to'g'ri holat kodlari, idempotentlik, sahifalash, izchil xato shakli va versiyalash.

🕑 10 daqiqa o‘qish 📄 689 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Resurslar - ot, fe'l emas
  2. Metodlar va amallar
  3. To'liq CRUD
  4. PUT va PATCH farqi
  5. Idempotentlik
  6. Sahifalash
  7. Izchil xato shakli
  8. Versiyalash
  9. Xulosa

REST - API qurishning eng keng tarqalgan uslubi. Uning asosiy g'oyasi: yo'l narsani bildiradi, metod esa u bilan nima qilishni.

Resurslar - ot, fe'l emas #

YomonYaxshi
GET /talabaOlish?id=5GET /talabalar/5
POST /talabaYaratishPOST /talabalar
POST /talabaOchirish/5DELETE /talabalar/5

Yo'lda fe'l bo'lmasin - metodning o'zi fe'l vazifasini bajaradi.

Metodlar va amallar #

MetodYo'lAmalHolat
GET/talabalarRo'yxat200
GET/talabalar/5Bittasi200 yoki 404
POST/talabalarYaratish201
PUT/talabalar/5To'liq almashtirish200
PATCH/talabalar/5Qisman o'zgartirish200
DELETE/talabalar/5O'chirish204

To'liq CRUD #

JavaScript
import express from "express";

const app = express();
app.use(express.json());

let keyingiId = 3;
const talabalar = new Map([
    [1, { id: 1, ism: "Malika", ball: 88 }],
    [2, { id: 2, ism: "Husanboy", ball: 92 }],
]);

app.get("/talabalar", (sorov, javob) => {
    javob.json({ malumot: [...talabalar.values()] });
});

app.get("/talabalar/:id", (sorov, javob) => {
    const talaba = talabalar.get(Number(sorov.params.id));
    if (!talaba) {
        javob.status(404).json({ xato: "talaba topilmadi" });
        return;
    }
    javob.json({ malumot: talaba });
});

app.post("/talabalar", (sorov, javob) => {
    const id = keyingiId++;
    const talaba = { id, ism: sorov.body.ism, ball: sorov.body.ball };
    talabalar.set(id, talaba);
    javob.status(201).location(`/talabalar/${id}`).json({ malumot: talaba });
});

app.patch("/talabalar/:id", (sorov, javob) => {
    const id = Number(sorov.params.id);
    const talaba = talabalar.get(id);
    if (!talaba) {
        javob.status(404).json({ xato: "talaba topilmadi" });
        return;
    }
    if (sorov.body.ball !== undefined) talaba.ball = sorov.body.ball;
    javob.json({ malumot: talaba });
});

app.delete("/talabalar/:id", (sorov, javob) => {
    talabalar.delete(Number(sorov.params.id));
    javob.status(204).end();
});

const server = app.listen(0);
const port = server.address().port;
const asos = `http://localhost:${port}`;

const sora = async (yol, sozlama = {}) => {
    const j = await fetch(asos + yol, {
        headers: { "Content-Type": "application/json" },
        ...sozlama,
    });
    const matn = await j.text();
    return [j.status, matn ? JSON.parse(matn) : null];
};

const royxat = await sora("/talabalar");
console.log("ro'yxat (oddiy):", royxat);
console.dir(royxat, { depth: null });

console.log("bittasi:", await sora("/talabalar/1"));
console.log("yo'q:   ", await sora("/talabalar/99"));

const yaratildi = await sora("/talabalar", {
    method: "POST",
    body: JSON.stringify({ ism: "Aziza", ball: 79 }),
});
console.log("yaratildi:", yaratildi);

console.log("o'zgardi:", await sora("/talabalar/3", {
    method: "PATCH",
    body: JSON.stringify({ ball: 85 }),
}));

console.log("o'chirildi:", await sora("/talabalar/3", { method: "DELETE" }));

server.close();
Natija
ro'yxat (oddiy): [ 200, { malumot: [ [Object], [Object] ] } ]
[
  200,
  {
    malumot: [
      { id: 1, ism: 'Malika', ball: 88 },
      { id: 2, ism: 'Husanboy', ball: 92 }
    ]
  }
]
bittasi: [ 200, { malumot: { id: 1, ism: 'Malika', ball: 88 } } ]
yo'q:    [ 404, { xato: 'talaba topilmadi' } ]
yaratildi: [ 201, { malumot: { id: 3, ism: 'Aziza', ball: 79 } } ]
o'zgardi: [ 200, { malumot: { id: 3, ism: 'Aziza', ball: 85 } } ]
o'chirildi: [ 204, null ]
console.log ichkariga faqat ikki qavat qaraydi

Birinchi qatorga e'tibor bering: [Object] chiqdi, haqiqiy ma'lumot emas.

Sabab - console.log obyektni ikki qavatgacha ko'rsatadi. Undan chuqurroqdagi hamma narsa [Object] yoki [Array] bo'lib qoladi.

Bu nosozlik izlashda juda ko'p vaqt yeydi: ma'lumot bor deb o'ylaysiz, lekin ko'rinmaydi.

Yechim ikkita:

JavaScript
console.dir(qiymat, { depth: null });
console.log(JSON.stringify(qiymat, null, 2));

Ikkinchi qator yuqorida aynan shu uchun ishlatilgan.

201 bilan Location sarlavhasi

POST yaratganda ikki narsa qaytariladi:

  1. 201 holati - "yaratildi".
  2. Location sarlavhasi - yangi resursning manzili.

javob.status(201).location("/talabalar/3") - Express da shunday yoziladi.

Bu mijozga yaratilgan narsani qayerdan topishni aytadi va standartga mos keladi.

PUT va PATCH farqi #

JavaScript
const mavjud = { id: 1, ism: "Malika", ball: 88, guruh: "A1" };

function putBilan(eski, yangi) {
    return { id: eski.id, ...yangi };
}

function patchBilan(eski, yangi) {
    return { ...eski, ...yangi };
}

const ozgarish = { ball: 95 };

console.log("PUT:  ", putBilan(mavjud, ozgarish));
console.log("PATCH:", patchBilan(mavjud, ozgarish));
Natija
PUT:   { id: 1, ball: 95 }
PATCH: { id: 1, ism: 'Malika', ball: 95, guruh: 'A1' }

PUT - to'liq almashtirish: yuborilmagan maydonlar yo'qoladi. PATCH - qisman: faqat yuborilganlar o'zgaradi.

Amalda PATCH ko'proq ishlatiladi, chunki mijoz odatda bitta maydonni o'zgartiradi.

Idempotentlik #

JavaScript
const holat = { soni: 0, ball: 50 };

function put(yangiBall) {
    holat.ball = yangiBall;
    return holat.ball;
}

function post() {
    holat.soni += 1;
    return holat.soni;
}

console.log("PUT uch marta:", put(90), put(90), put(90));
console.log("POST uch marta:", post(), post(), post());
Natija
PUT uch marta: 90 90 90
POST uch marta: 1 2 3
Idempotent nima degani

Amalni bir necha marta takrorlash bir marta bajarish bilan bir xil natija bersa, u idempotent deyiladi.

MetodIdempotentmi
GETHa
PUTHa
DELETEHa
PATCHOdatda yo'q
POSTYo'q

Nega muhim: tarmoq ishonchsiz. Mijoz so'rov yuboradi, javob yo'qoladi - u qayta yuborishi kerakmi?

PUT uchun ha, xavfsiz. POST uchun esa yo'q: ikkinchi urinish ikkinchi yozuv yaratadi.

Shuning uchun to'lov kabi muhim amallarda idempotentlik kaliti ishlatiladi: mijoz har bir amalga noyob kalit beradi va server takrorni taniydi.

Sahifalash #

JavaScript
import express from "express";

const app = express();

const hammasi = Array.from({ length: 47 }, (_, i) => ({
    id: i + 1,
    ism: `Talaba-${i + 1}`,
}));

app.get("/talabalar", (sorov, javob) => {
    const sahifa = Math.max(1, Number(sorov.query.sahifa ?? 1));
    const hajm = Math.min(50, Math.max(1, Number(sorov.query.hajm ?? 10)));

    const boshi = (sahifa - 1) * hajm;
    const bolak = hammasi.slice(boshi, boshi + hajm);

    javob.json({
        malumot: bolak,
        sahifalash: {
            sahifa,
            hajm,
            jami: hammasi.length,
            sahifalar: Math.ceil(hammasi.length / hajm),
        },
    });
});

const server = app.listen(0);
const port = server.address().port;
const asos = `http://localhost:${port}`;

const birinchi = await (await fetch(asos + "/talabalar")).json();
console.log(birinchi.sahifalash);
console.log("birinchi id:", birinchi.malumot[0].id);

const oxirgi = await (await fetch(asos + "/talabalar?sahifa=5")).json();
console.log("oxirgi sahifada:", oxirgi.malumot.length);

const katta = await (await fetch(asos + "/talabalar?hajm=999")).json();
console.log("hajm cheklandi:", katta.sahifalash.hajm);

server.close();
Natija
{ sahifa: 1, hajm: 10, jami: 47, sahifalar: 5 }
birinchi id: 1
oxirgi sahifada: 7
hajm cheklandi: 50
Sahifa hajmini cheklang

hajm parametri foydalanuvchidan keladi. Uni cheklamasangiz, kimdir ?hajm=1000000 deb so'rashi mumkin.

Natijada server butun jadvalni xotiraga yuklaydi va javob tayyorlashda hodisalar siklini bloklaydi (7-bo'lim). Bitta so'rov butun xizmatni to'xtatib qo'yishi mumkin.

Yuqorida Math.min(50, ...) bilan yuqori chegara qo'yilgan, Math.max(1, ...) esa manfiy va nol qiymatlardan himoya qiladi.

Har bir ro'yxat uchun odatiy va eng katta hajmni belgilang.

Izchil xato shakli #

JavaScript
import express from "express";

const app = express();
app.use(express.json());

class ApiXatosi extends Error {
    constructor(holat, kod, xabar, tafsilot = null) {
        super(xabar);
        this.holat = holat;
        this.kod = kod;
        this.tafsilot = tafsilot;
    }
}

app.get("/topilmadi", () => {
    throw new ApiXatosi(404, "TOPILMADI", "talaba topilmadi");
});

app.get("/tekshiruv", () => {
    throw new ApiXatosi(422, "TEKSHIRUV", "maydonlar noto'g'ri", {
        ball: "0 va 100 orasida bo'lsin",
    });
});

app.get("/kutilmagan", () => {
    throw new Error("baza yiqildi: parol=maxfiy");
});

app.use((xato, sorov, javob, keyingi) => {
    if (xato instanceof ApiXatosi) {
        javob.status(xato.holat).json({
            xato: { kod: xato.kod, xabar: xato.message,
                tafsilot: xato.tafsilot },
        });
        return;
    }
    javob.status(500).json({
        xato: { kod: "ICHKI_XATO", xabar: "kutilmagan xato",
            tafsilot: null },
    });
});

const server = app.listen(0);
const port = server.address().port;
const asos = `http://localhost:${port}`;

for (const yol of ["/topilmadi", "/tekshiruv", "/kutilmagan"]) {
    const j = await fetch(asos + yol);
    const tana = await j.json();
    console.log(j.status, tana.xato.kod, "|", tana.xato.xabar);
    console.log("   tafsilot:", JSON.stringify(tana.xato.tafsilot));
}

server.close();
Natija
404 TOPILMADI | talaba topilmadi
   tafsilot: null
422 TEKSHIRUV | maydonlar noto'g'ri
   tafsilot: {"ball":"0 va 100 orasida bo'lsin"}
500 ICHKI_XATO | kutilmagan xato
   tafsilot: null

Hamma xato bir xil shaklda qaytadi: kod, xabar, tafsilot.

Mijoz uchun bu juda qulay: bitta ishlovchi yozib, hamma xatoni qayta ishlash mumkin.

kod - mashina uchun (TOPILMADI), xabar - odam uchun. Mijoz kod bo'yicha qaror qabul qiladi, chunki xabar o'zgarishi yoki tarjima qilinishi mumkin.

Versiyalash #

JavaScript
import express from "express";

const app = express();

app.get("/v1/talabalar", (s, j) => {
    j.json({ malumot: [{ id: 1, ism: "Malika" }] });
});

app.get("/v2/talabalar", (s, j) => {
    j.json({
        malumot: [{ id: 1, ism: { birinchi: "Malika", oxirgi: "Karimova" } }],
    });
});

const server = app.listen(0);
const port = server.address().port;
const asos = `http://localhost:${port}`;

const v1 = await (await fetch(asos + "/v1/talabalar")).json();
const v2 = await (await fetch(asos + "/v2/talabalar")).json();

console.log(JSON.stringify(v1));
console.log(JSON.stringify(v2));

server.close();
Natija
{"malumot":[{"id":1,"ism":"Malika"}]}
{"malumot":[{"id":1,"ism":{"birinchi":"Malika","oxirgi":"Karimova"}}]}
Buzadigan o'zgarish - yangi versiya

API ni kimdir ishlatayotgan bo'lsa, javob shaklini o'zgartirib bo'lmaydi: ularning kodi buziladi.

Xavfsiz o'zgarishlar (versiya kerak emas):

  • Javobga yangi maydon qo'shish;
  • Yangi ixtiyoriy parametr qo'shish;
  • Yangi yo'l qo'shish.

Buzadigan o'zgarishlar (yangi versiya kerak):

  • Maydonni o'chirish yoki nomini o'zgartirish;
  • Maydon turini o'zgartirish (yuqorida ism satrdan obyektga aylandi);
  • Majburiy parametr qo'shish.

Yo'lda versiya (/v1/) - eng sodda usul va u brauzerda ham ko'rinadi.

Amaliy topshiriq
  1. Resurs uchun beshta CRUD marshrutini yozing.
  2. POST da 201 va Location sarlavhasini qaytaring.
  3. DELETE da 204 qaytaring.
  4. Yo'q resurs uchun 404 bering.
  5. PUT va PATCH farqini kod bilan ko'rsating.
  6. Qaysi metodlar idempotent ekanini sanab chiqing.
  7. Sahifalash qo'shing va hajmni cheklang.
  8. ?hajm=999999 yuborib, cheklanganini tekshiring.
  9. Izchil xato shaklini joriy qiling.
  10. /v1 va /v2 yo'llarini yonma-yon yozing.

Xulosa #

  • Yo'l - ot, metod - fe'l: DELETE /talabalar/5.
  • Yaratishda 201 va Location, o'chirishda 204.
  • PUT to'liq almashtiradi, PATCH qisman o'zgartiradi.
  • GET, PUT, DELETE idempotent, POST esa yo'q.
  • Sahifalashda hajmni albatta cheklang.
  • Hamma xato bir xil shaklda qaytsin: kod, xabar, tafsilot.
  • kod mashina uchun, xabar odam uchun.
  • Buzadigan o'zgarish - yangi versiya; maydon qo'shish esa xavfsiz.

Keyingi bo'limda ma'lumotlar bazasini ulaymiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.