Menu
CoddyTech

入力を検証する

CoddyのNode.jsジャーニー「REST API」セクションの一部。レッスン 6/25。

クライアントは本文に何でも送信でき、if (!name)で防げるのは空の値だけです。123、true、["x"]は真値なので、そのまま通過します。空白だけの文字列も通過します。' '.trim()は''になるため、trim()で検出しましょう。各フィールドの型をtypeofで確認してから、制限を確認します。

const { seats } = req.body ?? {};
if (typeof seats !== 'number' || !Number.isInteger(seats) || seats < 1) {
    return res.status(400).json({ error: 'seats must be a whole number from 1' });
}

応答を読む人に向けたメッセージを書きましょう。フィールド名を示し、何である必要があるかを伝えれば、何を修正すればよいか分かります。PATCHでは、送信されたフィールドだけを確認します。それ以外のフィールドは変更されないためです。

challenge icon

チャレンジ

簡単

現時点では、if (!title) では 123 や " " のようなタイトルも通ってしまいます。保存する前に、各フィールドの型とサイズを確認してください。

  1. routes/tasks.js の POST ルートで、title を確認します。
    • 文字列でなければなりません。
    • 空、または空白のみであってはなりません。
    • 空白も含めて、最大100文字でなければなりません。

    これらの条件のいずれかを満たさない場合は、ステータス400と、error に title という語を含む文を返します。例:

    { "error": "title must be a string of 1 to 100 characters" }
  2. PATCH ルートでは、リクエスト本文で送られたフィールドだけを確認します。
    • title が送られた場合は、同じルールと同じ種類のエラーを適用します。
    • done が送られた場合は、true または false でなければなりません。そうでない場合は、ステータス400と、done という語を含むエラーを返します。
    { "error": "done must be true or false" }
    拒否された PATCH では何も変更されません。
  3. 正しいリクエスト本文は引き続き機能します。POST は引き続き201を返し、{ "done": true } を含む PATCH は引き続き200を返します。PUT はそのままにしてください。スキーマも zod で検証されます。

試してみましょう: /api/tasks に { "title": 123 } を POST すると、タイトルのエラーとともに400が返ります。{ "title": " " } を POST した場合も、400が返ります。{ "done": "yes" } を含む PATCH /api/tasks/2 は、done のエラーとともに400を返し、GET /api/tasks/2 では引き続き "done": false が表示されます。{ "done": true } を含む PATCH /api/tasks/2 は200を返します。

自分で試してみよう

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

const router = Router();

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 ?? {};
    if (!title) {
        return res.status(400).json({ error: 'title is required' });
    }
    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) 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;
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

REST APIのすべてのレッスン

自分で練習してみよう: Expressプレイグラウンド