15-bo‘lim
REST API qurish
Resurslar va HTTP metodlari, to'g'ri holat kodlari, idempotentlik, sahifalash, izchil xato shakli va versiyalash.
Ushbu bo‘lim mundarijasi
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 #
| Yomon | Yaxshi |
|---|---|
GET /talabaOlish?id=5 | GET /talabalar/5 |
POST /talabaYaratish | POST /talabalar |
POST /talabaOchirish/5 | DELETE /talabalar/5 |
Yo'lda fe'l bo'lmasin - metodning o'zi fe'l vazifasini bajaradi.
Metodlar va amallar #
| Metod | Yo'l | Amal | Holat |
|---|---|---|---|
GET | /talabalar | Ro'yxat | 200 |
GET | /talabalar/5 | Bittasi | 200 yoki 404 |
POST | /talabalar | Yaratish | 201 |
PUT | /talabalar/5 | To'liq almashtirish | 200 |
PATCH | /talabalar/5 | Qisman o'zgartirish | 200 |
DELETE | /talabalar/5 | O'chirish | 204 |
To'liq CRUD #
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();
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 ]
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:
console.dir(qiymat, { depth: null });
console.log(JSON.stringify(qiymat, null, 2));
Ikkinchi qator yuqorida aynan shu uchun ishlatilgan.
POST yaratganda ikki narsa qaytariladi:
- 201 holati - "yaratildi".
Locationsarlavhasi - 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 #
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));
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 #
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());
PUT uch marta: 90 90 90
POST uch marta: 1 2 3
Amalni bir necha marta takrorlash bir marta bajarish bilan bir xil natija bersa, u idempotent deyiladi.
| Metod | Idempotentmi |
|---|---|
GET | Ha |
PUT | Ha |
DELETE | Ha |
PATCH | Odatda yo'q |
POST | Yo'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 #
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();
{ sahifa: 1, hajm: 10, jami: 47, sahifalar: 5 }
birinchi id: 1
oxirgi sahifada: 7
hajm cheklandi: 50
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 #
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();
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 #
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();
{"malumot":[{"id":1,"ism":"Malika"}]}
{"malumot":[{"id":1,"ism":{"birinchi":"Malika","oxirgi":"Karimova"}}]}
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
ismsatrdan obyektga aylandi); - Majburiy parametr qo'shish.
Yo'lda versiya (/v1/) - eng sodda usul va u brauzerda ham
ko'rinadi.
- Resurs uchun beshta CRUD marshrutini yozing.
POSTda 201 vaLocationsarlavhasini qaytaring.DELETEda 204 qaytaring.- Yo'q resurs uchun 404 bering.
PUTvaPATCHfarqini kod bilan ko'rsating.- Qaysi metodlar idempotent ekanini sanab chiqing.
- Sahifalash qo'shing va hajmni cheklang.
?hajm=999999yuborib, cheklanganini tekshiring.- Izchil xato shaklini joriy qiling.
/v1va/v2yo'llarini yonma-yon yozing.
Xulosa #
- Yo'l - ot, metod - fe'l:
DELETE /talabalar/5. - Yaratishda 201 va
Location, o'chirishda 204. PUTto'liq almashtiradi,PATCHqisman o'zgartiradi.GET,PUT,DELETEidempotent,POSTesa yo'q.- Sahifalashda hajmni albatta cheklang.
- Hamma xato bir xil shaklda qaytsin:
kod,xabar,tafsilot. kodmashina uchun,xabarodam uchun.- Buzadigan o'zgarish - yangi versiya; maydon qo'shish esa xavfsiz.
Keyingi bo'limda ma'lumotlar bazasini ulaymiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.