Sıralama
Coddy'nin Node.js Journey'sinin REST API'leri bölümünün bir parçası. Ders 16 / 25.
İstemci, önce en yeni görevleri veya A'dan Z'ye sıralanmış listeyi istiyor. Yaygın bir kural, tersine çevirmek için başına - eklenen ?sort=field biçimidir:
// GET /api/books?sort=-year
const field = sort.startsWith('-') ? sort.slice(1) : sort; // 'year'
const direction = sort.startsWith('-') ? -1 : 1;
list = list.toSorted((a, b) => (a[field] - b[field]) * direction);Önce field değerini izin verdiğiniz alanlarla karşılaştırın ve diğer tüm değerler için 400 yanıtı verin; çünkü kitaplarda bulunmayan bir alan kullanışlı bir sıralama sağlamaz. toSorted sıralanmış bir kopya döndürür. sort dizinin kendisini yeniden sıralar ve hiçbir filtre kopya oluşturmadığında bu dizi veri deposudur: sonraki her istek yeni sıralamayı görür. Bu örnek çıkarma işlemi yapar ve bu yalnızca sayılar için işe yarar. İki dizgeyi karşılaştırmak için a.localeCompare(b) kullanın: çıkarma gibi, negatif bir sayı, 0 veya pozitif bir sayı döndürür. ISO tarihleri, zaman sırasına göre sıralanan dizgelerdir.
Görev
KolayMetni Sıralama bölümünde yazdığın yalnızca başlığa göre sıralamayı, aşağıdaki alan ve yön işleme mantığıyla değiştir.
İstemciler en yeni görevleri ilk sırada veya listeyi A'dan Z'ye görmek istiyor. ?sort= ile listenin kendini sıralamasını sağla.
routes/tasks.jsdosyasında, liste rotasındareq.queryiçindensortdeğerini oku. Bu değer yoksa liste kayıtlı sırasını korur.?sort=titlebaşlığa göre A'dan Z'ye sıralar.?sort=createdAtoluşturulma zamanına göre, en eskiden başlayarak sıralar. Başına-eklenmesi sıralamayı tersine çevirir:?sort=-titleZ'den A'ya,?sort=-createdAtise en yeniden başlayarak sıralar.- Her iki alan da metindir.
createdAt,"2026-03-03T10:30:00.000Z"gibi bir ISO tarihidir ve metin olarak doğru şekilde sıralanır. Bu nedenle çıkarma işlemiyle değil,localeCompareile karşılaştır. ?sort=colorgibi başka herhangi bir alan, 400 durum kodunu döndürür:{ "error": { "code": "invalid_query", "message": "sort must be title or createdAt, with - in front to reverse" } }- Filtrelerden sonra sıralama yap; böylece
?done=false&sort=-title, açık görevleri Z'den A'ya sıralı olarak döndürür. toSortedile bir kopyayı sırala.sortkayıtlı dizinin kendisini yeniden sıralar ve sonraki her istek yeni sırayı görür.
Deneyin: GET /api/tasks?sort=title, 2, 3, 1 numaralı görevleri ("Book...", "Send...", "Write...") döndürür. GET /api/tasks?sort=-createdAt, 3, 2, 1 numaralı görevleri döndürür. Ardından sade GET /api/tasks, 1, 2, 3 numaralı görevleri yeniden döndürür. GET /api/tasks?sort=color, invalid_query koduyla birlikte 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 hâliyle tam 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();
// Büyük-küçük harf fark etmeksizin 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 } = 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 (req.query.sort === 'title') {
list = list.toSorted((a, b) => a.title.localeCompare(b.title));
}
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;
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