開発情報・ナレッジ

投稿者: 学生インターン 2026年4月24日 (金)

SPIRAL × Google Apps Script で手広く使える「疑似APIサーバー」を試してみる

学生インターンシップです!
スパイラル株式会社の学生インターンとして、SPIRALを学習しながらユーザー目線で質問に答える活動をしています。
補足情報や調査結果の提供を行い、皆様のお役立てができればと思っています!
※学生が学びの一環として調査・まとめた内容です。
正確な仕様や最新情報は、公式リファレンスなどをご確認ください。

はじめに

SPIRALでインターンをしている学生の私ですが、SPIRALで外部連携に挑戦しようとすると、 「API連携」「Webhook」「Google連携」 などの言葉をよく目にします。

今回は実際に手を動かして、API連携の基礎を学ぶ構築をしてみます!

この構築でやること
  • SPIRAL から 外部送信(Webhook) でデータを送る
  • 受け取り先として Google Apps Script(GAS) を使う
  • GAS を APIサーバーのように機能させる
  • 受け取ったデータをログに残す

「API連携の仕組みを理解すること」を目的として、作成してみます!

構成案

SPIRAL:フォーム送信・データ更新
↓ 外部送信 / POST
Google Apps Script(疑似API

スプレッドシートにログ保存

今回GAS の役割は、「SPIRAL と外部サービスの間に立つ中継サーバー」としました。

構築①:データ連携用のスプレッドシートを作成

今回は以下のようなシートを用意しました。



シート名(ここでは「log」)と、以下のシートIDを控えておきます。

https://docs.google.com/spreadsheets/d/【この部分】/edit?gid=0#gid=0


構築②:Google Apps Script の設定

2-1. GAS プロジェクトを作成

A) Google ドライブを開く
B) 新規 → その他 → Google Apps Script
C) プロジェクト名を設定


(3)Googleドライブ

(4)GAS作成

(5)プロジェクト名

2-2. Web API として使うための基本コードを入力する
(6)codebox

以下のコードを使用しています。

/**
 * SPIRAL -> GAS Webhook Receiver (Pseudo API)
 * 受信したJSONを検証して、スプレッドシートにログ保存する
 *
 * 期待するリクエスト(JSON):
 * {
 *   "recordId": "xxxxx",
 *   "token": "shared_secret",
 *   "event": "created",
 *   "sentAt": "2026-01-14T10:00:00Z"
 * }
 */

const SPREADSHEET_ID = "構築①で取得したシートID";
const SHEET_NAME = "構築①で作成したシート名";
const SHARED_TOKEN = "my_shared_secret_2026";

function doPost(e) {
  try {
    const raw = e?.postData?.contents || "";
    if (!raw) return json({ status: 400, ok: false, error: "Empty body" });

    let data;
    try {
      data = JSON.parse(raw);
    } catch (err) {
      return json({ status: 400, ok: false, error: "Invalid JSON", raw });
    }

    // 簡易認証
    if (!data.token || data.token !== SHARED_TOKEN) {
      return json({ status: 401, ok: false, error: "Unauthorized" });
    }

    const recordId = String(data.recordId || "").trim();
    if (!recordId) {
      return json({ status: 400, ok: false, error: "recordId is missing" });
    }

    // ログ保存
    const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
    if (!sheet) {
      return json({ status: 500, ok: false, error: `Sheet not found: ${SHEET_NAME}` });
    }

    // 受信ログ:受信時刻 / recordId / event / 生JSON
    sheet.appendRow([new Date(), recordId, String(data.event || ""), raw]);

    return json({ status: 200, ok: true, recordId });
  } catch (err) {
    console.error(err);
    return json({ status: 500, ok: false, error: String(err) });
  }
}

function json(obj) {
  return ContentService
    .createTextOutput(JSON.stringify(obj))
    .setMimeType(ContentService.MimeType.JSON);
}

/**
 * 初回権限付与用(必要なら一度実行)
 * スプレッドシートへのアクセス権限を先に通す
 */
function authCheck() {
  const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
  if (!sheet) throw new Error("Sheet not found");
  return sheet.getLastRow();
}

2-3. Webアプリとして公開する

A) 右上「デプロイ」→「新しいデプロイ」
B) 種類:ウェブアプリ
C) 実行ユーザー:自分
D) アクセス権:全員
E) デプロイ → URL をコピー:「疑似APIサーバーのエンドポイント」


(7)デプロイ

(8)新しいデプロイ

(9)ウェブアプリ設定

(10)URLコピー

これにてGASの準備が完了です。

構築③:SPIRAL 側の外部送信設定

SPIRAL の外部送信は、特定のイベントが起きたら指定URLにデータをPOSTします。
今回は、以下のように構築していきます。

  • トリガ:フォーム新規登録時、データ更新時
  • 送信先URL:GAS の WebアプリURL
3-1. 作成済アプリのDBメニューから、トリガを作成

(11)トリガ作成

今回使用するのは非同期アクションです。

3-2. 非同期アクションの設定を行う

(12)非同期アクション設定

3-3. 「+」をクリックし、「PHP実行」を選択

(13)PHP実行選択

3-4. 表示名や経路条件などを設定し、PHP本文を記述

使用したコードは以下の通りです。

<?php
/**
 * SPIRAL ver.2 DBトリガ(登録) -> GAS WebアプリへPOST
 * - recordId を最小送信
 * - GASの302リダイレクトに追従(Moved Temporarily 対策)
 * - レスポンスJSONの成功判定を柔軟に行う
 */

define('GAS_WEBHOOK_URL', '構築②で作成したGASのウェブアプリURL');
define('SHARED_TOKEN', 'my_shared_secret_2026'); // GAS側と一致させる

if (!isset($SPIRAL)) {
  throw new Exception('SPIRAL runtime is not available.');
}

/** 1) レコード取得 */
$record = $SPIRAL->getRecord();
$item   = $record['item'] ?? null;

if (!is_array($item)) {
  throw new Exception('Record is empty.');
}

/** 2) レコードID(通常 _id) */
$recordId = (string)($item['_id'] ?? '');
if ($recordId === '') {
  throw new Exception('Record ID is missing.');
}

/** 3) 送信payload(最小+拡張しやすい項目) */
$payload = [
  'recordId' => $recordId,
  'token'    => SHARED_TOKEN,
  'event'    => 'created',
  'sentAt'   => date('c'),
];

/** 4) GASへPOST(302追従) */
$ch = curl_init(GAS_WEBHOOK_URL);

curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_TIMEOUT, 10);

// ★重要:GASが302を返す場合があるので追従する
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err      = curl_error($ch);
curl_close($ch);

if ($response === false) {
  throw new Exception('cURL Error: ' . $err);
}

// 2xx以外はエラー(追従後の結果で判定)
if ($httpCode < 200 || $httpCode >= 300) {
  throw new Exception("HTTP Error: {$httpCode}, body={$response}");
}

/** 5) レスポンスJSONを確認 */
$decoded = json_decode($response, true);
if (!is_array($decoded)) {
  throw new Exception('Invalid JSON response: ' . $response);
}

// 成功判定
$isOk =
  (($decoded['ok'] ?? false) === true) ||
  ((int)($decoded['status'] ?? 0) === 200) ||
  (($decoded['status'] ?? null) === 'ok');

if (!$isOk) {
  throw new Exception('GAS returned error: ' . $response);
}

3-5. 「保存」をクリックして完了

(14)保存完了

非同期アクションが完成しました。
動作確認をしてみます!

作成した非同期アクションをクリックして、「編集」をクリックします。


(15)編集

コードを記述したテキストボックスの下、「手動実行」をクリックします。
レコードIDを入力して「実行」をクリックすると、アクションが実行されます。

(16)手動実行

(17)実行画面

スプレッドシートを確認すると、登録情報が記録されています!

(18)実行結果

(19)シート確認

また、このDBで作成したサイトから情報を登録すると、スプレッドシートに登録情報が更新されています!

(20)サイト登録

(21)更新反映

■SPIRAL → GAS → Googleスプレッドシートの一連の流れが、正しく動作していることが確認できました!

まとめ

本記事では実際に手を動かして、API連携の基礎を学ぶ構築を試してみました!
コードの記述にはAIの力を借りてもいますが、なんとか思い描いていた動作を実現できてうれしいです!

今回の構築で、APIサーバーがどういったものか実感することができました。
GASでの構築は無料で試せるので、私のような初心の練習場としてかなりおすすめです。

  • API = 難しいサーバーではない
  • POSTを受けて、JSONを返す

今回はスプレッドシートのデータ更新を目標にしましたが、以下のようにステップアップしてみるとより面白そうです!

  • 保存データの整形
  • Googleサービスへのデータ連携
  • Teams / Slack 連携
解決しない場合はこちら コンテンツに関しての
要望はこちら