Menu
CoddyTech

Text sortieren

Teil des Abschnitts REST-APIs der Node.js-Journey von Coddy. Lektion 15 von 25.

Dein Tracker kann Aufgaben filtern. Lass jetzt einen Client alphabetische Titel anfordern, ohne die gespeicherte Reihenfolge zu ändern. Ein Sortiervergleich gibt eine negative Zahl zurück, wenn sein erstes Element vor das zweite gehört, eine positive Zahl, wenn es danach gehört, und null, wenn sie gleichwertig sind.

const sorted = cities.toSorted((a, b) => a.name.localeCompare(b.name));

Das Subtrahieren von Zeichenfolgen ergibt keine sinnvolle Reihenfolge. localeCompare vergleicht stattdessen Text. toSorted gibt ein neues Array zurück und lässt das ursprüngliche Array unverändert. Sortiere die gefilterte Liste, damit Filtern und Sortieren zusammenwirken. Die folgende Lektion zum Sortieren behandelt weitere Felder, absteigende Reihenfolge und den Umgang mit ungültigen Abfragen.

challenge icon

Aufgabe

Einfach

Setze deinen Task-Tracker in routes/tasks.js fort. Sorge in der Listenroute nach den vorhandenen Filtern dafür, dass ?sort=title die Aufgaben alphabetisch nach Titel zurückgibt. Ohne diesen Wert soll das aktuelle Verhalten erhalten bleiben. Ändere nicht die gespeicherte Aufgabenreihenfolge.

Probiere GET /api/tasks?sort=title und danach GET /api/tasks aus. Die zweite Liste muss weiterhin in ihrer ursprünglichen Reihenfolge sein. Das Filtern mit ?done=false&sort=title muss weiterhin nur unerledigte Aufgaben zurückgeben.

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 nicht aufgelistet sind.
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;

Alle Lektionen in REST-APIs

Übe selbstständig: Express-Playground