アプリ間のデータ連携

にメンテナンス済み

kintone を業務で活用していると、「案件管理アプリのデータを日報アプリにコピーしたい」「マスターアプリの情報を参照して自動入力したい」といった、複数アプリ間でデータを連携したい場面が頻繁に発生します。

kintone の標準機能ではルックアップ関連レコード一覧でアプリ間の参照ができますが、より柔軟なデータ連携を実現するには JavaScript カスタマイズと REST API の組み合わせが有効です。

この記事では、kintone の複数アプリ間でデータを連携する代表的なパターンと、その実装方法を紹介します。

基本:別アプリのレコードを取得する

アプリ間連携の基本は、REST API で別アプリのレコードを取得することです。kintone.api を使えば、現在のアプリだけでなく、任意のアプリのレコードにアクセスできます。

get-other-app-records.js
/**
 * 指定したアプリからレコードを取得する
 * @param { Object } params
 * @param { string | number } params.app - 取得先のアプリID
 * @param { string } [params.query] - クエリ
 * @param { string[] } [params.fields] - 取得するフィールドコード
 * @returns { Promise<{ records: Record<string, any>[] }> }
 */
const getRecords = (params) => {
  const { app, query = '', fields = [] } = params;
  const body = { app, query };
  if (fields.length > 0) {
    body.fields = fields;
  }
  return kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', body);
};
アプリIDの管理

連携先のアプリ ID はコード内にハードコーディングせず、定数として管理することをお勧めします。環境(開発・本番)によってアプリ ID が異なる場合があるため、変更しやすい形にしておくと保守性が向上します。

定数定義
定数定義は本当に様々な方法で実装可能なため、どのように実装するのがベストなのか、今でもよく悩むところです。今回は、僕が実際に採用してうまくいった方法を紹介したいと思います。

パターン 1:マスターアプリからのデータ参照

商品マスターや顧客マスターのようなマスターアプリからデータを取得し、別のアプリのフィールドに自動入力するパターンです。ルックアップで実現できる範囲を超える、複雑な条件での参照が必要な場合に有効です。

master-reference.js
(() => {
  'use strict';

  // マスターアプリのID
  const PRODUCT_MASTER_APP_ID = 10;

  /**
   * 商品コードから商品マスターのレコードを取得する
   * @param { string } productCode - 商品コード
   * @returns { Promise<Record<string, any> | null> }
   */
  const getProduct = async (productCode) => {
    const query = `商品コード = "${productCode}" limit 1`;
    const { records } = await kintone.api(
      kintone.api.url('/k/v1/records.json', true),
      'GET',
      { app: PRODUCT_MASTER_APP_ID, query }
    );
    return records.length > 0 ? records[0] : null;
  };

  // 商品コードが変更されたら、マスターから情報を取得して自動入力
  const events = [
    'app.record.create.change.商品コード',
    'app.record.edit.change.商品コード',
  ];

  kintone.events.on(events, async (event) => {
    const record = event.record;
    const productCode = record['商品コード'].value;

    if (!productCode) {
      record['商品名'].value = '';
      record['単価'].value = '';
      return event;
    }

    const product = await getProduct(productCode);
    if (product) {
      record['商品名'].value = product['商品名'].value;
      record['単価'].value = product['単価'].value;
      record['カテゴリ'].value = product['カテゴリ'].value;
    } else {
      record['商品名'].value = '';
      record['単価'].value = '';
      event.error = `商品コード「${productCode}」に一致する商品が見つかりません`;
    }

    return event;
  });
})();
チェック

ルックアップフィールドでは「コピー元のフィールド」が固定ですが、JavaScript カスタマイズでは複数の条件を組み合わせた検索や、取得後の加工処理を自由に実装できます。

パターン 2:レコードの保存時に別アプリへ自動登録

注文アプリにレコードを追加した際に、自動的に在庫アプリのデータを更新する、あるいは履歴アプリにログを残すといったパターンです。

auto-sync-on-save.js
(() => {
  'use strict';

  const HISTORY_APP_ID = 20;

  /**
   * 履歴アプリにレコードを追加する
   * @param { Object } params
   * @param { string } params.action - 操作種別
   * @param { string } params.detail - 詳細
   * @param { string | number } params.sourceRecordId - 元レコードID
   * @returns { Promise<{ id: string, revision: string }> }
   */
  const addHistory = (params) => {
    const { action, detail, sourceRecordId } = params;
    return kintone.api(kintone.api.url('/k/v1/record.json', true), 'POST', {
      app: HISTORY_APP_ID,
      record: {
        操作種別: { value: action },
        詳細: { value: detail },
        元レコードID: { value: String(sourceRecordId) },
        操作者: { value: [{ code: kintone.getLoginUser().code }] },
        操作日時: { value: new Date().toISOString() },
      },
    });
  };

  // レコード保存成功後に履歴を追加
  const events = [
    'app.record.create.submit.success',
    'mobile.app.record.create.submit.success',
  ];

  kintone.events.on(events, async (event) => {
    const record = event.record;
    try {
      await addHistory({
        action: '新規作成',
        detail: `案件「${record['案件名'].value}」が作成されました`,
        sourceRecordId: event.recordId,
      });
    } catch (error) {
      console.error('履歴の追加に失敗しました:', error);
    }
    return event;
  });
})();
submit イベントと submit.success イベントの違い

submit イベント内で別アプリへの登録を行うと、元のレコードの保存が失敗した場合でも登録が実行されてしまいます。データの整合性を保つには、submit.success イベント(保存成功後)を使用するか、bulkRequest でアトミックに実行することを検討してください。

bulkRequest
kintone REST API の bulkRequest を使って、複数のAPI操作を1回のリクエストでまとめて実行する方法を解説します。トランザクション的な一括処理、アプリ間のデータ同期、デリートインサートの安全な実装方法を紹介します

パターン 3:アプリ間の集計

別アプリのデータを集計して、現在のアプリに表示するパターンです。例えば、顧客アプリのレコード詳細画面に、その顧客の注文合計金額を表示するケースです。

cross-app-aggregation.js
(() => {
  'use strict';

  const ORDER_APP_ID = 30;

  /**
   * 指定した顧客コードの注文合計金額を取得する
   * @param { string } customerCode - 顧客コード
   * @returns { Promise<number> } 合計金額
   */
  const getTotalAmount = async (customerCode) => {
    const query = `顧客コード = "${customerCode}" and ステータス in ("完了")`;
    const { records } = await kintone.api(
      kintone.api.url('/k/v1/records.json', true),
      'GET',
      {
        app: ORDER_APP_ID,
        query,
        fields: ['金額'],
      }
    );
    return records.reduce((sum, record) => sum + Number(record['金額'].value), 0);
  };

  const events = ['app.record.detail.show', 'mobile.app.record.detail.show'];

  kintone.events.on(events, async (event) => {
    const record = event.record;
    const customerCode = record['顧客コード'].value;

    if (!customerCode) {
      return event;
    }

    const totalAmount = await getTotalAmount(customerCode);

    // スペースフィールドに集計結果を表示
    const spaceEl = kintone.app.record.getSpaceElement('集計表示エリア');
    if (spaceEl) {
      spaceEl.innerHTML = '';
      const div = document.createElement('div');
      div.style.cssText = 'padding: 8px; background: #f0f9ff; border-radius: 4px; font-weight: bold;';
      div.textContent = `注文合計金額: ¥${totalAmount.toLocaleString()}`;
      spaceEl.appendChild(div);
    }

    return event;
  });
})();
スペースフィールドの活用
kintone のスペースフィールドを活用して、ボタン、ステータス表示、入力フォーム、グラフなどのカスタム要素を画面内に配置する方法を解説します。kintone.app.record.getSpaceElement の基本的な使い方から実践
チェック

取得対象のレコード数が多い場合は、records.json の 500 件制限に注意してください。全件取得が必要な場合は、カーソル API やレコード ID を使ったシーク法を検討しましょう。

カーソルAPI
kintone のカーソル API を使って 10,000 件を超える大量レコードを安全に取得する方法を解説します。カーソルの作成・取得・削除の基本操作から、全件取得の汎用関数、運用上の注意点まで紹介します。
レコードIDを使った一括取得
kintoneにはAPIを使ってレコード情報を取得する方法が複数用意されています。今回はその中でもシーク法と呼ばれる、レコードIDをもとに一括取得する方法を紹介します。

パターン 4:レコードの一括コピー

あるアプリのレコードを別のアプリに一括でコピーするパターンです。例えば、テンプレートアプリから実際のプロジェクトアプリにタスクを複製する場合などに使用します。

bulk-copy.js
(() => {
  'use strict';

  const TEMPLATE_APP_ID = 40;
  const TASK_APP_ID = kintone.app.getId();

  /**
   * テンプレートからタスクを一括コピーする
   * @param { string } templateCategory - テンプレートのカテゴリ
   * @param { string } projectName - プロジェクト名
   */
  const copyFromTemplate = async (templateCategory, projectName) => {
    // Step 1: テンプレートのレコードを取得
    const query = `カテゴリ = "${templateCategory}" order by 表示順 asc`;
    const { records: templates } = await kintone.api(
      kintone.api.url('/k/v1/records.json', true),
      'GET',
      { app: TEMPLATE_APP_ID, query }
    );

    if (templates.length === 0) {
      throw new Error(`テンプレート「${templateCategory}」が見つかりません`);
    }

    // Step 2: コピー用のレコードデータを構築
    const newRecords = templates.map((template) => ({
      タスク名: { value: template['タスク名'].value },
      説明: { value: template['説明'].value },
      担当者: { value: template['デフォルト担当者'].value },
      期限日数: { value: template['標準日数'].value },
      プロジェクト名: { value: projectName },
      ステータス: { value: '未着手' },
    }));

    // Step 3: 100件ずつ分割して登録(REST API の上限対応)
    const chunkSize = 100;
    for (let i = 0; i < newRecords.length; i += chunkSize) {
      const chunk = newRecords.slice(i, i + chunkSize);
      await kintone.api(kintone.api.url('/k/v1/records.json', true), 'POST', {
        app: TASK_APP_ID,
        records: chunk,
      });
    }

    return newRecords.length;
  };

  kintone.events.on(['app.record.index.show'], (event) => {
    // ヘッダーにコピーボタンを設置
    if (document.getElementById('copy-template-btn')) {
      return event;
    }

    const headerEl = kintone.app.getHeaderMenuSpaceElement();
    const button = document.createElement('button');
    button.id = 'copy-template-btn';
    button.textContent = 'テンプレートからタスクを作成';
    button.className = 'kintoneplugin-button-dialog-ok';
    button.addEventListener('click', async () => {
      const category = window.prompt('テンプレートのカテゴリを入力してください');
      const project = window.prompt('プロジェクト名を入力してください');
      if (!category || !project) return;

      try {
        button.disabled = true;
        button.textContent = 'コピー中...';
        const count = await copyFromTemplate(category, project);
        window.alert(`${count} 件のタスクを作成しました`);
        location.reload();
      } catch (error) {
        console.error(error);
        window.alert(`エラーが発生しました: ${error.message}`);
      } finally {
        button.disabled = false;
        button.textContent = 'テンプレートからタスクを作成';
      }
    });
    headerEl.appendChild(button);

    return event;
  });
})();
POST, PUT, DELETEの上限
kintone REST APIには、GET, POST, PUT, DELETEそれぞれに、1度に操作できるレコードの上限が設けられています。今回は上記のレコード上限を気にすることなく、一括でレコードの作成ができる関数をご紹介します。RE

アプリ間連携の設計ポイント

アプリ ID の一元管理

複数のアプリを連携する場合、アプリ ID をファイルの先頭で定数として定義します。

app-config.js
// アプリID定数(環境に応じて変更)
const APP_IDS = Object.freeze({
  顧客マスター: 10,
  商品マスター: 11,
  注文管理: 20,
  在庫管理: 21,
  履歴: 30,
});

エラーハンドリングの統一

アプリ間連携では、連携先アプリの API 呼び出しが失敗するケースを必ず考慮します。

error-handling-pattern.js
/**
 * アプリ間連携で推奨されるエラーハンドリングパターン
 */
const safeApiCall = async (apiFunc, fallbackMessage) => {
  try {
    return await apiFunc();
  } catch (error) {
    console.error(fallbackMessage, error);
    // 連携先のエラーが元のレコード操作に影響しないようにする
    return null;
  }
};
チェック

連携先アプリへのアクセス権がない場合、REST API はエラーを返します。連携を設計する際は、カスタマイズを利用するすべてのユーザーが連携先アプリの閲覧権限(または書き込み権限)を持っていることを確認してください。

まとめ

  • kintone の REST API を使えば、JavaScript カスタマイズから任意のアプリのデータにアクセスできる
  • マスターデータの参照は、フィールド変更イベントで別アプリを検索し、結果を自動入力するパターンが基本
  • レコード保存時の自動連携submit.success イベントを使うことでデータの整合性を保てる
  • アプリ間の集計処理は、スペースフィールドを活用して結果を表示する
  • 連携先のアプリ ID は定数として一元管理し、エラーハンドリングも統一する
  • データの整合性が重要な場合は bulkRequest によるアトミックな操作を検討する

関連記事

レコードの取得(複数件)
kintone REST APIを使用して、レコードを複数件取得する方法を紹介します。レコードIDを指定して、レコード情報を取得することができます。
レコードの追加(複数件)
kintone REST APIを使用して、レコードを複数件追加する方法を紹介します。Node.jsやGASでのサンプルコードも掲載しています。
bulkRequest
kintone REST API の bulkRequest を使って、複数のAPI操作を1回のリクエストでまとめて実行する方法を解説します。トランザクション的な一括処理、アプリ間のデータ同期、デリートインサートの安全な実装方法を紹介します
スペースフィールドの活用
kintone のスペースフィールドを活用して、ボタン、ステータス表示、入力フォーム、グラフなどのカスタム要素を画面内に配置する方法を解説します。kintone.app.record.getSpaceElement の基本的な使い方から実践
定数定義
定数定義は本当に様々な方法で実装可能なため、どのように実装するのがベストなのか、今でもよく悩むところです。今回は、僕が実際に採用してうまくいった方法を紹介したいと思います。

レコードの保存と同時に別アプリへデータを登録する場合、データの整合性を保つために最も適切な方法はどれですか?

REST API で別アプリからレコードを取得する際、500 件以上のレコードが必要な場合の対処法として適切なものはどれですか?

#kintone #JavaScript #REST API