Pengurutan
Termasuk bagian REST API dalam perjalanan Node.js di Coddy. Pelajaran 16 dari 25.
client menginginkan tugas terbaru di urutan pertama, atau daftar dari A sampai Z. Konvensi yang umum adalah ?sort=field, dengan - di depan untuk membalik urutannya:
// 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);Periksa field terhadap field yang kamu izinkan terlebih dahulu, lalu berikan jawaban 400 untuk field lainnya, karena field yang tidak dimiliki buku tidak menghasilkan urutan yang berguna. toSorted mengembalikan salinan yang sudah diurutkan. sort mengurutkan ulang array itu sendiri. Jika tidak ada filter yang membuat salinan, array tersebut adalah penyimpanan data: setiap permintaan berikutnya akan melihat urutan baru. Contoh ini menggunakan pengurangan, yang hanya berfungsi untuk angka. Untuk membandingkan dua string, gunakan a.localeCompare(b): seperti pengurangan, fungsi ini mengembalikan angka negatif, 0, atau angka positif. Tanggal ISO adalah string yang akan diurutkan berdasarkan urutan waktu.
Tantangan
MudahGanti pengurutan yang hanya berdasarkan judul yang kamu tulis di Sorting Text dengan penanganan field dan arah pengurutan di bawah ini.
Client ingin tugas terbaru ditampilkan lebih dulu, atau list diurutkan dari A sampai Z. Biarkan list mengurutkan dirinya dengan ?sort=.
- Di
routes/tasks.js, pada route list, bacasortdarireq.query. Tanpanya, list mempertahankan urutan penyimpanannya. ?sort=titlemengurutkan berdasarkan judul dari A sampai Z.?sort=createdAtmengurutkan berdasarkan waktu pembuatan, dari yang paling lama. Tanda-di depan membalik urutan:?sort=-titlemengurutkan dari Z ke A, dan?sort=-createdAtmenampilkan yang terbaru lebih dulu.- Kedua field berupa teks.
createdAtadalah tanggal ISO seperti"2026-03-03T10:30:00.000Z", yang diurutkan dengan benar sebagai teks. Jadi, bandingkan denganlocaleCompare, bukan dengan pengurangan. - Field lain apa pun, seperti
?sort=color, menghasilkan status 400:{ "error": { "code": "invalid_query", "message": "sort must be title or createdAt, with - in front to reverse" } } - Lakukan pengurutan setelah filter, agar
?done=false&sort=-titlemengembalikan tugas yang belum selesai dari Z ke A. - Urutkan salinan dengan
toSorted.sortmengubah urutan array yang tersimpan itu sendiri, dan setiap permintaan berikutnya akan melihat urutan baru tersebut.
Coba: GET /api/tasks?sort=title mengembalikan tugas 2, 3, 1 ("Book...", "Send...", "Write..."). GET /api/tasks?sort=-createdAt mengembalikan 3, 2, 1. Lalu, GET /api/tasks biasa mengembalikan 1, 2, 3 lagi. GET /api/tasks?sort=color mengembalikan 400 dengan kode invalid_query.
Coba sendiri
import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';
const router = Router();
// Task lengkap, seperti yang dikirim oleh PUT. .strict() menolak field yang tidak tercantum.
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();
// Bernilai true jika task lain sudah memiliki title ini, tanpa membedakan huruf besar dan kecil.
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;
Semua pelajaran di REST API
1Sumber Daya dan Kata Kerja
Sumber Daya dan URLMembuat dengan POST4Peramban, Versi, dan Konfigurasi
Apa Itu OriginMengizinkan Origin LainCORSHalaman yang Memanggil APILatihan sendiri: Playground Express