Menu
CoddyTech

Mengurutkan Teks

Termasuk bagian REST API dalam perjalanan Node.js di Coddy. Pelajaran 15 dari 25.

Pelacakmu dapat memfilter tugas. Sekarang, biarkan client meminta judul dalam urutan alfabet tanpa mengubah urutan yang tersimpan. Perbandingan pengurutan menghasilkan angka negatif ketika item pertama berada sebelum item kedua, angka positif ketika item pertama berada setelah item kedua, dan nol ketika keduanya setara.

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

Mengurangi string tidak menghasilkan urutan yang berguna. localeCompare membandingkan teks. toSorted mengembalikan array baru dan membiarkan array asli tetap tidak berubah. Urutkan list yang sudah difilter agar pemfilteran dan pengurutan dapat bekerja bersama. Pelajaran Sorting berikutnya menambahkan field lain, urutan menurun, dan penanganan kueri yang tidak valid.

challenge icon

Tantangan

Mudah

Lanjutkan pelacak tugasmu di routes/tasks.js. Pada rute daftar, setelah filter yang sudah ada, buat ?sort=title mengembalikan tugas yang diurutkan secara alfabetis berdasarkan judul. Tanpa nilai itu, pertahankan perilaku saat ini. Jangan ubah urutan tugas yang tersimpan.

Coba GET /api/tasks?sort=title, lalu GET /api/tasks. Daftar kedua harus tetap dalam urutan aslinya. Pemfilteran dengan ?done=false&sort=title harus tetap hanya mengembalikan tugas yang belum selesai.

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));
    }
    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

Latihan sendiri: Playground Express