Sortieren
Teil des Abschnitts REST-APIs der Node.js-Journey von Coddy. Lektion 11 von 18.
Ein Client möchte die neuesten Aufgaben zuerst oder die Liste von A bis Z. Eine gängige Konvention ist ?sort=field, mit einem - davor, um die Reihenfolge umzukehren:
// GET /api/books?sort=-year
const field = sort.replace(/^-/, ''); // 'year'
const direction = sort.startsWith('-') ? -1 : 1;
list = list.toSorted((a, b) => (a[field] - b[field]) * direction);Prüfe zuerst, ob field zu den erlaubten Feldern gehört, und antworte bei jedem anderen Wert mit 400, da ein Feld, das Bücher nicht haben, keine nützliche Sortierung ergibt. toSorted gibt eine sortierte Kopie zurück. sort sortiert das Array selbst um. Wenn kein Filter eine Kopie erstellt hat, ist dieses Array der Speicher: Jede spätere Anfrage sieht die neue Reihenfolge. Vergleiche Zeichenfolgen mit a.localeCompare(b). ISO-Daten sind Zeichenfolgen, die chronologisch sortiert werden.
Aufgabe
Einfach?sort=titlesortiert von A bis Z,?sort=createdAtvom ältesten zum neuesten; ein vorangestelltes-kehrt die Reihenfolge um.- Bei jedem anderen Feld wird mit dem Code
invalid_queryder Status 400 zurückgegeben. - Das Sortieren ändert niemals die gespeicherte Reihenfolge.
Sende GET /api/tasks?sort=-createdAt, dann GET /api/tasks, dann GET /api/tasks?sort=color.
Probier es selbst
import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';
const router = Router();
// Eine vollständige Aufgabe, wie PUT sie sendet. .strict() lehnt Felder ab, die es nicht aufführt.
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();
// True, wenn eine andere Aufgabe diesen Titel bereits hat, unabhängig von der Groß- und Kleinschreibung.
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));
}
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;