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.
Tantangan
Mudah- Ganti pemeriksaan manual dengan skema
Task:titleberupa string yang sudah dihapus spasi di awal dan akhirnya, sepanjang 1 hingga 100 karakter,doneberupa boolean,.strict(). - POST (
doneopsional, nilai defaultfalse), PATCH (semua opsional), dan PUT menggunakansafeParse; jika body tidak valid, responsnya adalah 400 denganz.flattenError(result.error)sebagaierror.
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
1Sumber Daya dan Kata Kerja
Sumber Daya dan URLMembuat dengan POSTLatihan sendiri: Playground Express