Ordinare il testo
Fa parte della sezione API REST del percorso Node.js di Coddy. Lezione 15 di 25.
Il tuo tracker può filtrare le attività. Ora consenti a un client di richiedere titoli in ordine alfabetico senza modificare l’ordine memorizzato. Un confronto di ordinamento restituisce un numero negativo quando il primo elemento deve precedere il secondo, un numero positivo quando deve seguirlo e zero quando sono equivalenti.
const sorted = cities.toSorted((a, b) => a.name.localeCompare(b.name));Sottrarre stringhe non fornisce un ordine utile. localeCompare confronta invece il testo. toSorted restituisce un nuovo array, lasciando invariato quello originale. Ordina l’elenco filtrato, così il filtraggio e l’ordinamento funzionano insieme. La seguente lezione sull’ordinamento aggiunge altri campi, l’ordine decrescente e la gestione delle query non valide.
Sfida
FacileContinua il tuo gestore di attività in routes/tasks.js. Nella route dell’elenco, dopo i filtri esistenti, fai in modo che ?sort=title restituisca le attività in ordine alfabetico per titolo. Senza quel valore, mantieni il comportamento attuale. Non modificare l’ordine delle attività memorizzate.
Prova GET /api/tasks?sort=title, poi GET /api/tasks. Il secondo elenco deve essere ancora nell’ordine originale. Il filtro con ?done=false&sort=title deve continuare a restituire solo le attività non completate.
Provalo tu
import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';
const router = Router();
// Un'attività completa, così come viene inviata da PUT. .strict() rifiuta i campi non elencati.
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 se un'altra attività ha già questo titolo, indipendentemente dalle maiuscole e dalle minuscole.
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;
Tutte le lezioni di API REST
1Risorse e verbi
Risorse e URLCreare con POST4Browser, versioni e configurazione
Che cos'è un'origineConsentire un'altra origineCORSUna pagina che chiama l'APIEsercitati da solo: Playground Express