Menu
CoddyTech

Skema dengan zod

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

Pemeriksaan manual bertambah seiring bertambahnya setiap field dan setiap route. Schema zod menjelaskan bentuknya satu kali, lalu safeParse menguji nilai berdasarkan schema tersebut:

import { z } from 'zod';

const Event = z.object({
    name: z.string().trim().min(1).max(80),
    seats: z.number().int().min(1),
}).strict();

const result = Event.safeParse(req.body);
if (!result.success) {
    return res.status(400).json({ error: z.flattenError(result.error) });
}
// result.data menyimpan body yang sudah diperiksa, dengan name yang sudah dipangkas

.strict() menolak field yang tidak tercantum dalam schema. Event.partial() membuat setiap field bersifat opsional, sesuai kebutuhan PATCH, sedangkan Event.partial({ seats: true }) hanya menjadikan seats opsional. z.flattenError mencantumkan masalah berdasarkan field. Event.parse melakukan pemeriksaan yang sama, tetapi melempar error jika nilainya tidak valid, dan error yang dilempar di route akan menjadi 500.

challenge icon

Tantangan

Mudah
  • Ganti pemeriksaan manual dengan skema Task: title berupa string yang sudah dihapus spasi di awal dan akhirnya, sepanjang 1 hingga 100 karakter, done berupa boolean, .strict().
  • POST (done opsional, nilai default false), PATCH (semua opsional), dan PUT menggunakan safeParse; jika body tidak valid, responsnya adalah 400 dengan z.flattenError(result.error) sebagai error.

Kirim POST dengan { "title": " Order new chairs ", "color": "red" }, lalu kirim tanpa color.

Coba sendiri

import { Router } from 'express';
import { nextId, tasks } from '../store.js';

const router = Router();

// Alasan title tidak dapat disimpan, atau null jika dapat disimpan.
const titleError = (title) => {
    if (typeof title !== 'string' || title.trim() === '' || title.length > 100) {
        return 'title must be a string of 1 to 100 characters';
    }
    return null;
};

router.get('/', (req, res) => {
    res.json(tasks);
});

router.get('/:id', (req, res) => {
    const task = tasks.find((t) => t.id === Number(req.params.id));
    if (!task) {
        return res.status(404).json({ error: 'Task not found' });
    }
    res.json(task);
});

router.post('/', (req, res) => {
    const { title } = req.body ?? {};
    const problem = titleError(title);
    if (problem) {
        return res.status(400).json({ error: problem });
    }
    const task = { id: nextId(), title, done: false, 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 res.status(404).json({ error: 'Task not found' });
    }
    const { title, done } = req.body ?? {};
    if (title !== undefined && titleError(title)) {
        return res.status(400).json({ error: titleError(title) });
    }
    if (done !== undefined && typeof done !== 'boolean') {
        return res.status(400).json({ error: 'done must be true or false' });
    }
    if (title !== undefined) task.title = title;
    if (done !== undefined) task.done = done;
    res.json(task);
});

router.put('/:id', (req, res) => {
    const index = tasks.findIndex((t) => t.id === Number(req.params.id));
    if (index === -1) {
        return res.status(404).json({ error: 'Task not found' });
    }
    const { title, done } = req.body ?? {};
    if (title === undefined || done === undefined) {
        return res.status(400).json({ error: 'PUT needs every field: title and done' });
    }
    const { id, createdAt } = tasks[index];
    tasks[index] = { id, title, done, 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 res.status(404).json({ error: 'Task not found' });
    }
    tasks.splice(index, 1);
    res.status(204).end();
});

export default router;

Semua pelajaran di REST API

Latihan sendiri: Playground Express