本ページはプロモーションが含まれています。

API設計の基本:任意項目が空のとき、どう送る?

1. はじめに

フロントエンドの開発をしていると、登録フォームの実装で「任意項目が空のとき、APIにどう送信すればいいんだろう?」と悩むことありませんか?

例えば、ユーザー登録画面で以下のような項目があったとします:

  • ユーザー名(必須)
  • ニックネーム(任意)
  • 年齢(任意)

ニックネームが未入力のとき、APIに以下のどちらで送るべきでしょうか?

// パターンA: 空文字として送る
?username=taro&nickname=&age=25

// パターンB: パラメータごと送らない
?username=taro&age=25

2. 任意項目が空なら送らないのが一般的

最初に結論からお伝えします。任意項目が空の場合は、パラメータごと送らないのが一般的です。

// ❌ 空文字を含める(非推奨)
?username=taro&nickname=&age=25

// ⭕️ 空の項目は送らない(推奨)
?username=taro&age=25

次のセクションから、なぜこの方法が推奨されるのか、その理由を詳しく見ていきましょう。

3. なぜ「送らない」のが推奨されるのか?

3-1. 意図が明確に伝わる

空文字を送る方法では、「値がない」ことと「空文字という値がある」ことを区別できません。一方、パラメータ自体を送らない方法なら、「ユーザーが何も入力しなかった」という意図が明確に伝わります。

// バックエンド側での判断
// パターンA: 空文字が送られてきた場合
if (nickname === '') {
  // これは「空文字を入力した」のか「未入力」なのか?
}

// パターンB: パラメータが存在しない場合
if (nickname === undefined) {
  // 明らかに「未入力」だと分かる
}

この違いは、特に検索機能で重要になります。例えば、商品検索で「名前が空文字のものを探す」のと「名前での絞り込みをしない」のは、全く異なる動作です。

3-2. 無駄なデータ送信を避ける

空のパラメータを送らないことで、以下のメリットがあります:

  • ネットワークトラフィックの削減:データ量が減り、通信が効率的になります
  • URLが短く読みやすくなる:ログやデバッグ時に見やすくなります
  • セキュリティ上も有利:不要な情報を送らないことで、攻撃対象を減らせます

3-3. バックエンドの実装がシンプルに

バックエンド側では、「パラメータが存在しない = デフォルト値を使う」という実装が書きやすくなります。

// バックエンド側の実装例
const nickname = params.nickname ?? 'ゲスト';  // 未入力ならデフォルト値
const age = params.age;                         // 送られてきたら使う

この実装パターンは、Null合体演算子(??)やOptional Chainingなど、現代のプログラミング言語の機能とも相性が良く、コードがシンプルで読みやすくなります。

3-4. 業界標準のベストプラクティス

API設計の世界では、「パラメータの存在/不在でデータの状態を表現する」という考え方が広く受け入れられています。OpenAPI仕様でもoptional/nullableの扱いが定義されており、多くのAPI設計ガイドラインで「任意項目が空の場合はパラメータを送らない」ことが推奨されています。

4. 実装方法:基本編

それでは、実際にどうコードを書けばいいのか見ていきましょう。

4-1. シンプルな実装

最も基本的な実装方法は、条件分岐で空の項目をチェックすることです。

const registerUser = async (formData: FormData) => {
  // 送信するパラメータを準備
  const params: Record<string, any> = {
    username: formData.username,  // 必須項目は必ず含める
  };

  // ニックネームが入力されている場合のみ追加
  if (formData.nickname && formData.nickname.trim() !== '') {
    params.nickname = formData.nickname;
  }

  // 年齢が入力されている場合のみ追加
  if (formData.age !== undefined && formData.age !== '') {
    params.age = formData.age;
  }

  // APIにリクエスト送信
  await api.post('/users', params);
};

この方法は分かりやすいですが、項目が増えると条件分岐が増えて見通しが悪くなります。

4-2. より実用的な実装

ヘルパー関数を用意して共通化する方法もあります。

// utils/apiHelpers.ts

/**
 * オブジェクトから空の値を持つプロパティを除外する
 * @param data 処理対象のオブジェクト
 * @returns 空でない値のみを含む新しいオブジェクト
 */
export const removeEmptyFields = <T extends Record<string, any>>(
  data: T
): Partial<T> => {
  return Object.entries(data).reduce((acc, [key, value]) => {
    // 除外する値のパターン
    // - 空文字 ('')
    // - null
    // - undefined
    // ※ 0 は意味のある値なのでそのまま残す
    if (value === 0 || (value !== '' && value !== null && value !== undefined)) {
      acc[key] = value;
    }
    return acc;
  }, {} as Partial<T>);
};

4-3. 使用例

import { removeEmptyFields } from './utils/apiHelpers';

const formData = {
  username: 'taro',
  nickname: '',        // 空文字
  age: 25,
  bio: null,          // null
  score: 0,           // 0点(これは残す)
};

const params = removeEmptyFields(formData);
console.log(params);
// 結果: { username: 'taro', age: 25, score: 0 }
// nickname と bio は除外されている

このヘルパー関数を使えば、プロジェクト全体で一貫した処理ができます。新しいメンバーが参加しても迷うことがありませんし、コードレビューもスムーズになります。

5. 数値型の任意項目はどうする?

文字列の場合は比較的分かりやすいのですが、数値型の任意項目はどう扱えばいいのでしょうか。

5-1. 0をデフォルトとして送る?

// ❌ こうすると問題が起きる
const formData = {
  username: 'taro',
  age: 0,           // 未入力時に0を設定
};

この実装には大きな問題があります。「0」は意味のある値なので、「未入力」なのか「0と入力した」のか区別できなくなってしまうのです。

5-2. 0が意味を持つ具体例

以下のような場面で、0は重要な意味を持ちます:

// 年齢の例
age: 0  // 「0歳の赤ちゃん」を表す?それとも「年齢未入力」?

// 価格の例
price: 0  // 「無料(0円)」?それとも「価格未設定」?

// 在庫数の例
stock: 0  // 「在庫なし(売り切れ)」?それとも「在庫数未入力」?

// 評価点の例
rating: 0  // 「最低評価(0点)」?それとも「未評価」?

これらのケースでは、「0」と「未入力」を明確に区別する必要があります。

5-3. 推奨される実装

数値型の任意項目も、未入力の場合は送らないのが適切です。

const formData = {
  username: 'taro',
  age: undefined,      // 未入力(送信しない)
  score: 0,           // 0点と明示的に入力した(送信する)
  rating: null,       // 未評価(送信しない)
};

const params = removeEmptyFields(formData);
// 結果: { username: 'taro', score: 0 }

5-4. フロントエンドとバックエンドでの判断

フロントエンド側

// 未入力の場合
const formData = {
  username: 'taro',
  age: undefined,  // または null を設定
};

// 0を入力した場合
const formData = {
  username: 'taro',
  age: 0,          // 明示的に0を送る
};

バックエンド側

// Node.js/Express の例

// パラメータが存在しない場合
if (req.body.age === undefined) {
  // 未入力 → デフォルト値を使う or nullとしてDBに保存
  user.age = null;
}

// 0が送られてきた場合
if (req.body.age === 0) {
  // ユーザーが「0」と入力した → そのまま保存
  user.age = 0;
}

このように、パラメータの「存在」「不在」で判断することで、「未入力」と「0を入力」を明確に区別できます。

6. まとめ

この記事では、API設計における任意項目の扱い方について解説しました。重要なポイントをおさらいしましょう。

基本原則

  • 任意項目が空の場合は送信しないのが一般的
  • 0は意味のある値として送信する
  • 「送られてこない = 未入力」という考え方がAPI設計の基本

メリット

  • データの意図が明確に伝わる
  • 無駄なデータ送信を避けられる
  • バックエンドの実装がシンプルになる
  • データベースとの整合性が保ちやすい

実装のコツ

  • ヘルパー関数を共通化してチーム全体で使用する
  • コーディング規約やAPI仕様書に明記する
  • テストコードでエッジケースをカバーする
  • レビュー時のチェックポイントを明確にする

実際のプロジェクトで実装する際は、チームメンバーと認識を合わせながら、一貫性のあるコードを心がけていきましょう。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする