Sayfalama
Coddy'nin Node.js Journey'sinin REST API'leri bölümünün bir parçası. Ders 12 / 18.
On bin görevden oluşan bir liste tek bir yanıt için çok uzundur. Sayfalar hâlinde gönderin: istemci ?page=2&limit=20 ister ve bu aralığı, ayrıca “5 sayfanın 2. sayfası” ifadesini göstermek için gerekenleri 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 değerini filtrelerden sonra hesaplayın; çünkü bu değer, istemcinin istediği listeyi tanımlar. page ve limit değerlerini Number ile okuyun ve aralıkta tam sayı olmayan her şeyi 400 hatasıyla reddedin. limit için belirlenen bir üst sınır, istemcinin her şeyi bir kerede istemesini önler.
Görev
KolayGET /api/tasks,{ items, page, total, pages }yanıtını verir.?page=1'den başlayan bir tam sayıdır;?limit=1 ile 50 arasındadır, varsayılan değer 10'dur.- Diğer tüm durumlarda
invalid_querykoduyla 400 yanıtı verilir.
GET /api/tasks?limit=2, GET /api/tasks?page=2&limit=2, ardından GET /api/tasks?page=0 isteğini gönder.
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(), listesinde olmayan 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 farkı 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).replace(/^-/, '');
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.