zod ile Şemalar
Coddy'nin Node.js Journey'sinin REST API'leri bölümünün bir parçası. Ders 11 / 25.
Elle yapılan kontroller her alan ve her uç noktayla birlikte artar. Bir zod şeması biçimi bir kez tanımlar ve safeParse bir değeri bu şemaya göre sınar:
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, name alanındaki boşlukları kırpılmış doğrulanmış body'yi içerir.strict(), şemada listelenmeyen alanları reddeder. Event.partial(), tüm alanları isteğe bağlı hâle getirir; PATCH için gereken de budur. Event.partial({ seats: true }) yalnızca seats alanını isteğe bağlı hâle getirir ve const { seats = 1 } = result.data, bu alan eksik olduğunda 1 değerini kullanır. z.flattenError(result.error), sorunları { formErrors, fieldErrors } biçimine dönüştürür: formErrors, bilinmeyen bir alan gibi tüm gövdeyle ilgili sorunları içerir; fieldErrors ise her alana ait sorunları içerir. Event.parse aynı kontrolü yapar ancak geçersiz bir değerle karşılaştığında hata fırlatır; bir uç noktada fırlatılan hata 500 yanıtına dönüşür.
Görev
KolayBu adım, Validating Input dersindeki proje dosyalarınız yerine kurs için hazırlanmış görev takip API projesini açar (bunların arasında routes/tasks.js de vardır). Önceki çalışmalarınız kendi dersinde kalır. routes/tasks.js dosyasını düzenlemeden önce sağlanan dosyaları okuyun. Bu kopyada görevler için CRUD rotaları ve elle yazılmış girdi doğrulaması bulunur.
Elle yazdığınız kontroller, her alan ve her rotayla birlikte artar. Bir görevi bir kez zod şemasıyla tanımlayın ve gövde alan üç rotanın da bu şemayı kullanmasını sağlayın.
routes/tasks.jsiçinde,zöğesinizodiçinden içe aktarın veTaskadlı bir şema yazın:title: bir dize; baştaki ve sondaki boşluklar kaldırıldıktan sonra 1 ile 100 karakter arasında olmalıdır.done:trueveyafalse(z.boolean()).- Şema katıdır; bu nedenle listede bulunmayan
colorgibi bir alan reddedilir.
Sağlanan
titleErroryardımcı işlevini silin (kursun Validating Input dersindeki doğrulama sürümünü).Tasköğesinden.partial()kullanarak iki sürüm oluşturun. POST sürümünde yalnızcadoneisteğe bağlı olur. PATCH sürümünde her iki alan da isteğe bağlı olur. PUT,Tasköğesinin kendisini kullanır; bu nedenle her iki alanı da gerektirir.- POST, PATCH ve PUT içinde
req.bodydeğerinisafeParseile kontrol edin. Kontrol başarısız olursa 400 durum koduyla yanıt verin ve zod'un sorunlar listesinierroraltında,z.flattenError(result.error)ile oluşturulmuş şekilde gönderin.{ "title": 123 }için sonuç şöyle görünür:{ "error": { "formErrors": [], "fieldErrors": { "title": ["Invalid input: expected string, received number"] } } } - Kontrol başarılı olduğunda
req.bodyyerineresult.dataiçinden kaydedin: bu değer, baştaki ve sondaki boşlukları kaldırılmış başlığı içerir. POST işleminde,doneiçermeyen bir gövde görevi"done": falseile oluşturur (eksik alanresult.dataiçindeundefinedolur ve??bir yedek değer sağlar). PUT, kayıtlı görevdekiidvecreatedAtdeğerlerini korur.
Deneyin: { "title": " Order new chairs ", "color": "red" } değerini /api/tasks adresine POST edin. 400 alırsınız ve formErrors, Unrecognized key: "color" der. color olmadan yeniden gönderin: "title": "Order new chairs" (boşluksuz) ve "done": false ile 201 alırsınız. { "done": "yes" } ile PATCH /api/tasks/2 isteği, fieldErrors içinde done girdisiyle 400 döndürür. { "title": "Book the big room", "done": false } ile PUT /api/tasks/2 isteği, görev 2'nin eski createdAt değeriyle 200 döndürür.
Kendin dene
import { Router } from 'express';
import { nextId, tasks } from '../store.js';
const router = Router();
// title'ın saklanamama nedeni; saklanabiliyorsa null.
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;
REST API'leri bölümündeki tüm dersler
1Kaynaklar ve Fiiller
Kaynaklar ve URL'lerPOST ile Oluşturma4Tarayıcılar, Sürümler ve Yapılandırma
Origin NedirBaşka Bir Origin'e İzin VermeCORSAPI'yi Çağıran Bir Sayfa2Doğrulama ve Hatalar
Girdiyi DoğrulamaKendi başına pratik yap: Express playground