Sayfalama
Coddy'nin Node.js Journey'sinin REST API'leri bölümünün bir parçası. Ders 17 / 25.
On bin görevden oluşan bir liste, tek bir yanıt için fazla uzundur. Listeyi sayfalara bölün: istemci ?page=2&limit=20 ister ve bu aralığı, ayrıca “5 sayfadan 2. sayfa” ifadesini göstermek için gereken bilgileri alır:
{ "items": [ ...books 21 to 40... ], "page": 2, "total": 95, "pages": 5 }const start = (page - 1) * limit; // page 1, 0'dan başlar
const items = list.slice(start, start + limit);
const pages = Math.ceil(list.length / limit);total sayısını filtrelerden sonra hesaplayın; çünkü bu sayı, istemcinin istediği listeyi tanımlar. Sorgu dizesinde page yoksa dönüştürme işleminden önce 1, limit içinse 10 kullanın: Number(undefined) değeri NaN olur. Ardından, aralıkta olmayan veya tam sayı olmayan her değeri 400 hatasıyla reddedin. limit için bir üst sınır belirlemek, istemcinin her şeyi bir kerede istemesini önler.
Görev
KolayOn bin görevden oluşan bir liste tek bir yanıt için çok uzundur. Listeyi sayfalar hâlinde gönderin; böylece istemci "Page 1 of 2" ifadesini gösterebilir.
routes/tasks.jsiçinde liste rotasının döndürdüğü şeyi değiştirin: dizi yerine bir nesne döndürsün. Başlangıç görevleri içinGET /api/tasks?limit=2şu yanıtı döndürür:{ "items": [ { "id": 1, "title": "Write the project brief", "done": true, "createdAt": "2026-03-02T09:00:00.000Z" }, { "id": 2, "title": "Book the meeting room", "done": false, "createdAt": "2026-03-03T10:30:00.000Z" } ], "page": 1, "total": 3, "pages": 2 }itemsbu sayfanın görevlerini tutar,pagesayfa numarasıdır,totalfiltrelerle eşleşen görevlerin sayısıdır vepagestoplam sayfa sayısıdır.- Sorgu dizgesinden
pagevelimitdeğerlerini okuyun:page1'den başlayan bir tam sayıdır. Belirtilmezse 1 kullanın.limit1 ile 50 arasında bir tam sayıdır. Belirtilmezse 10 kullanın.
Dönüştürmeden önce varsayılan değeri kullanın; çünkü
Number(undefined)değeriNaNolur. Diğer tüm değerler (page=0,page=abc,page=1.5,limit=0,limit=51) durum kodu 400 ile sonuçlanır; hatalı bir limit için mesajlimit must be a whole number from 1 to 50olur:{ "error": { "code": "invalid_query", "message": "page must be a whole number from 1" } } - Sayfalama işlemini en son yapın: önce filtreleyip sıralayın, sonra sayfayı ayırın.
total, filtrelerden sonra kalan görevleri sayar; bu nedenle?done=false&limit=1isteği"total": 2ve"pages": 2değerlerini verir.
Bu, yanıtın yapısını değiştirir; dolayısıyla bir dizi bekleyen istemcinin de değişmesi gerekir. Sürümleme dersi bunun nasıl yapılacağını gösterir.
Deneyin: GET /api/tasks?limit=2, "page": 1, "total": 3 ve "pages": 2 değerleriyle 1. ve 2. görevleri döndürür. GET /api/tasks?page=2&limit=2 yalnızca 3. görevi döndürür. Parametresiz GET /api/tasks, "page": 1 ve "pages": 1 değerleriyle 3 görevin tamamını döndürür. GET /api/tasks?page=0, invalid_query koduyla 400 döndürür.
Kendin dene
import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';
const router = Router();
// PUT tarafından gönderildiği biçimiyle eksiksiz bir görev. .strict(), listelemediği alanları reddeder.
const Task = z
.object({
title: z.string().trim().min(1).max(100),
done: z.boolean(),
})
.strict();
const NewTask = Task.partial({ done: true });
const TaskChanges = Task.partial();
const SORT_FIELDS = ['title', 'createdAt'];
// Büyük-küçük harf ayrımı gözetmeksizin başka bir görev zaten bu başlığa sahipse true.
const titleTaken = (title, exceptId) =>
tasks.some((t) => t.id !== exceptId && t.title.toLowerCase() === title.toLowerCase());
router.get('/', (req, res) => {
const { done, q, sort } = req.query;
let list = tasks;
if (done !== undefined) {
if (done !== 'true' && done !== 'false') {
return sendError(res, 400, 'invalid_query', 'done must be true or false');
}
list = list.filter((t) => t.done === (done === 'true'));
}
if (q !== undefined) {
const words = String(q).toLowerCase();
list = list.filter((t) => t.title.toLowerCase().includes(words));
}
if (sort !== undefined) {
const field = String(sort).startsWith('-') ? String(sort).slice(1) : String(sort);
if (!SORT_FIELDS.includes(field)) {
return sendError(
res,
400,
'invalid_query',
'sort must be title or createdAt, with - in front to reverse',
);
}
const direction = String(sort).startsWith('-') ? -1 : 1;
list = list.toSorted((a, b) => a[field].localeCompare(b[field]) * direction);
}
res.json(list);
});
router.get('/:id', (req, res) => {
const task = tasks.find((t) => t.id === Number(req.params.id));
if (!task) {
return sendError(res, 404, 'not_found', 'Task not found');
}
res.json(task);
});
router.post('/', (req, res) => {
const result = NewTask.safeParse(req.body);
if (!result.success) {
return sendInvalid(res, result.error);
}
const { title, done = false } = result.data;
if (titleTaken(title)) {
return sendError(res, 409, 'conflict', 'A task with this title already exists');
}
const task = { id: nextId(), title, done, createdAt: new Date().toISOString() };
tasks.push(task);
res.status(201).location(`/api/tasks/${task.id}`).json(task);
});
router.patch('/:id', (req, res) => {
const task = tasks.find((t) => t.id === Number(req.params.id));
if (!task) {
return sendError(res, 404, 'not_found', 'Task not found');
}
const result = TaskChanges.safeParse(req.body);
if (!result.success) {
return sendInvalid(res, result.error);
}
if (result.data.title !== undefined && titleTaken(result.data.title, task.id)) {
return sendError(res, 409, 'conflict', 'A task with this title already exists');
}
Object.assign(task, result.data);
res.json(task);
});
router.put('/:id', (req, res) => {
const index = tasks.findIndex((t) => t.id === Number(req.params.id));
if (index === -1) {
return sendError(res, 404, 'not_found', 'Task not found');
}
const result = Task.safeParse(req.body);
if (!result.success) {
return sendInvalid(res, result.error);
}
const { id, createdAt } = tasks[index];
if (titleTaken(result.data.title, id)) {
return sendError(res, 409, 'conflict', 'A task with this title already exists');
}
tasks[index] = { id, ...result.data, createdAt };
res.json(tasks[index]);
});
router.delete('/:id', (req, res) => {
const index = tasks.findIndex((t) => t.id === Number(req.params.id));
if (index === -1) {
return sendError(res, 404, 'not_found', 'Task not found');
}
tasks.splice(index, 1);
res.status(204).end();
});
export default router;
Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.
REST API'leri bölümündeki tüm dersler
1Kaynaklar ve Fiiller
Kaynaklar ve URL'lerPOST ile Oluşturma4Tarayıcılar, Sürümler ve Yapılandırma
Origin NedirBaşka Bir Origin'e İzin VermeCORSAPI'yi Çağıran Bir SayfaKendi başına pratik yap: Express playground