Menu
CoddyTech

Pengurutan

Termasuk bagian REST API dalam perjalanan Node.js di Coddy. Pelajaran 11 dari 18.

client ingin tugas terbaru ditampilkan lebih dulu, atau list diurutkan dari A sampai Z. Konvensi yang umum digunakan adalah ?sort=field, dengan - di depannya untuk membalik urutan:

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

Periksa field terlebih dahulu terhadap field yang kamu izinkan, lalu berikan respons 400 untuk field lainnya, karena field yang tidak dimiliki books 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. Bandingkan string dengan a.localeCompare(b). Tanggal ISO adalah string yang diurutkan sesuai urutan waktu.

challenge icon

Tantangan

Mudah
  • ?sort=title mengurutkan dari A sampai Z, ?sort=createdAt mengurutkan dari yang paling lama; tanda - di depan membalik urutan.
  • Field lain apa pun menghasilkan kode 400 dengan kode invalid_query.
  • Pengurutan tidak pernah mengubah urutan yang tersimpan.

Kirim GET /api/tasks?sort=-createdAt, lalu GET /api/tasks, lalu GET /api/tasks?sort=color.

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

// True jika task lain sudah memiliki judul 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