1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Google SpreadsheetをREST APIに変える10分チュートリアル

1
Last updated at Posted at 2025-12-21

はじめに

「簡単なデータベースが欲しいけど、サーバー立てるのは面倒…」

そんなとき、Google Spreadsheetが超便利です。

この記事では、SpreadsheetをREST API化する方法を10分で解説します。

完成イメージ

# データ取得
curl https://script.google.com/.../exec?action=get

# データ追加
curl -X POST https://script.google.com/.../exec \
  -d '{"action":"add","data":{"name":"田中","age":25}}'

コスト: 0円(Google無料枠)

前提条件

  • Googleアカウント
  • JavaScriptの基礎知識

Step 1: Spreadsheet作成(1分)

1-1. 新規Spreadsheet作成

https://sheets.google.com/ → 「空白」

1-2. データ構造を決める

| id | name | age | created_at |
|----|------|-----|------------|
| 1  | 山田 | 30  | 2025-12-21 |
| 2  | 佐藤 | 25  | 2025-12-21 |

1-3. ヘッダー行を入力

A1: id
B1: name
C1: age
D1: created_at

Step 2: Apps Script作成(3分)

2-1. Apps Scriptエディタを開く

拡張機能Apps Script

2-2. コードを貼り付け

function doGet(e) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const action = e.parameter.action;
  
  if (action === 'getAll') {
    return getAllData(sheet);
  } else if (action === 'getById') {
    return getDataById(sheet, e.parameter.id);
  }
  
  return ContentService.createTextOutput(
    JSON.stringify({ error: 'Invalid action' })
  ).setMimeType(ContentService.MimeType.JSON);
}

function getAllData(sheet) {
  const data = sheet.getDataRange().getValues();
  const headers = data[0];
  const rows = data.slice(1);
  
  const result = rows.map(row => {
    const obj = {};
    headers.forEach((header, index) => {
      obj[header] = row[index];
    });
    return obj;
  });
  
  return ContentService.createTextOutput(
    JSON.stringify(result)
  ).setMimeType(ContentService.MimeType.JSON);
}

function getDataById(sheet, id) {
  const data = sheet.getDataRange().getValues();
  const headers = data[0];
  const row = data.find(r => r[0] == id);
  
  if (!row) {
    return ContentService.createTextOutput(
      JSON.stringify({ error: 'Not found' })
    ).setMimeType(ContentService.MimeType.JSON);
  }
  
  const result = {};
  headers.forEach((header, index) => {
    result[header] = row[index];
  });
  
  return ContentService.createTextOutput(
    JSON.stringify(result)
  ).setMimeType(ContentService.MimeType.JSON);
}

2-3. POST対応(データ追加)

function doPost(e) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const data = JSON.parse(e.postData.contents);
  
  if (data.action === 'add') {
    return addData(sheet, data.data);
  } else if (data.action === 'update') {
    return updateData(sheet, data.id, data.data);
  } else if (data.action === 'delete') {
    return deleteData(sheet, data.id);
  }
  
  return ContentService.createTextOutput(
    JSON.stringify({ error: 'Invalid action' })
  ).setMimeType(ContentService.MimeType.JSON);
}

function addData(sheet, data) {
  const lastRow = sheet.getLastRow();
  const newId = lastRow; // 簡易的なID生成
  
  sheet.appendRow([
    newId,
    data.name,
    data.age,
    new Date()
  ]);
  
  return ContentService.createTextOutput(
    JSON.stringify({ success: true, id: newId })
  ).setMimeType(ContentService.MimeType.JSON);
}

function updateData(sheet, id, data) {
  const dataRange = sheet.getDataRange();
  const values = dataRange.getValues();
  
  const rowIndex = values.findIndex(row => row[0] == id);
  
  if (rowIndex === -1) {
    return ContentService.createTextOutput(
      JSON.stringify({ error: 'Not found' })
    ).setMimeType(ContentService.MimeType.JSON);
  }
  
  sheet.getRange(rowIndex + 1, 2).setValue(data.name || values[rowIndex][1]);
  sheet.getRange(rowIndex + 1, 3).setValue(data.age || values[rowIndex][2]);
  
  return ContentService.createTextOutput(
    JSON.stringify({ success: true })
  ).setMimeType(ContentService.MimeType.JSON);
}

function deleteData(sheet, id) {
  const dataRange = sheet.getDataRange();
  const values = dataRange.getValues();
  
  const rowIndex = values.findIndex(row => row[0] == id);
  
  if (rowIndex === -1) {
    return ContentService.createTextOutput(
      JSON.stringify({ error: 'Not found' })
    ).setMimeType(ContentService.MimeType.JSON);
  }
  
  sheet.deleteRow(rowIndex + 1);
  
  return ContentService.createTextOutput(
    JSON.stringify({ success: true })
  ).setMimeType(ContentService.MimeType.JSON);
}

Step 3: デプロイ(2分)

3-1. デプロイ設定

デプロイ新しいデプロイ

3-2. 設定

  • 種類: ウェブアプリ
  • 実行ユーザー: 自分
  • アクセス: 全員

3-3. URLを取得

https://script.google.com/macros/s/XXXXX/exec

このURLがあなたのREST APIです!

Step 4: 動作確認(2分)

4-1. 全データ取得

curl "https://script.google.com/macros/s/XXXXX/exec?action=getAll"

レスポンス:

[
  {"id":1,"name":"山田","age":30,"created_at":"2025-12-21"},
  {"id":2,"name":"佐藤","age":25,"created_at":"2025-12-21"}
]

4-2. ID指定で取得

curl "https://script.google.com/macros/s/XXXXX/exec?action=getById&id=1"

4-3. データ追加

curl -X POST https://script.google.com/macros/s/XXXXX/exec \
  -H "Content-Type: application/json" \
  -d '{"action":"add","data":{"name":"鈴木","age":28}}'

4-4. データ更新

curl -X POST https://script.google.com/macros/s/XXXXX/exec \
  -H "Content-Type: application/json" \
  -d '{"action":"update","id":1,"data":{"age":31}}'

4-5. データ削除

curl -X POST https://script.google.com/macros/s/XXXXX/exec \
  -H "Content-Type: application/json" \
  -d '{"action":"delete","id":2}'

Step 5: パフォーマンス最適化(2分)

5-1. バッチ処理

遅い(1件ずつ書き込み):

data.forEach(row => {
  sheet.appendRow(row);
});

速い(一括書き込み):

sheet.getRange(lastRow + 1, 1, data.length, 4).setValues(data);

効果: 10倍高速化

5-2. キャッシュの活用

const cache = CacheService.getScriptCache();

function getAllDataCached(sheet) {
  const cached = cache.get('allData');
  if (cached) {
    return ContentService.createTextOutput(cached)
      .setMimeType(ContentService.MimeType.JSON);
  }
  
  const data = getAllData(sheet);
  cache.put('allData', data.getContent(), 300); // 5分キャッシュ
  return data;
}

応用例

1. ToDoアプリのバックエンド

// フロントエンド(React)
async function getTodos() {
  const res = await fetch('https://script.google.com/.../exec?action=getAll');
  return res.json();
}

async function addTodo(title) {
  await fetch('https://script.google.com/.../exec', {
    method: 'POST',
    body: JSON.stringify({
      action: 'add',
      data: { title, done: false }
    })
  });
}

2. 簡易CMS

// ブログ記事管理
function getPosts(sheet) {
  // postsシートから記事一覧を取得
}

function getPostBySlug(sheet, slug) {
  // スラッグで記事を取得
}

3. フォーム送信先

<form id="contactForm">
  <input name="name" required>
  <input name="email" type="email" required>
  <textarea name="message" required></textarea>
  <button>送信</button>
</form>

<script>
document.getElementById('contactForm').addEventListener('submit', async (e) => {
  e.preventDefault();
  const formData = new FormData(e.target);
  const data = Object.fromEntries(formData);
  
  await fetch('https://script.google.com/.../exec', {
    method: 'POST',
    body: JSON.stringify({
      action: 'add',
      data: data
    })
  });
  
  alert('送信完了!');
});
</script>

4. リアルタイム監視システム

私が実際に運用している実装例を紹介します。

Tokyo VPN Speed Monitor

15種類のVPNを6時間ごとに自動測定し、データをSpreadsheetに蓄積。それをREST APIとして公開しているシステムです。

// 6時間ごとに実行
function measureAndStore() {
  const vpns = getVPNList();
  const results = [];
  
  vpns.forEach(vpn => {
    const speed = testSpeed(vpn);
    results.push([new Date(), vpn.name, speed.download, speed.upload]);
  });
  
  // バッチ書き込み(高速)
  const sheet = SpreadsheetApp.getActiveSheet();
  sheet.getRange(lastRow + 1, 1, results.length, 4).setValues(results);
}

// REST API化
function doGet(e) {
  const data = sheet.getDataRange().getValues();
  return ContentService.createTextOutput(JSON.stringify(data))
    .setMimeType(ContentService.MimeType.JSON);
}

実績:

  • 運用期間: 2週間+
  • 稼働率: 99.8%
  • 測定回数: 645+回
  • 運用コスト: 0円/月

GitHub: https://github.com/hmy0210/vpn-stability-ranking (MIT License)

制限事項

1. 実行時間

  • 制限: 6分/回
  • 対策: 処理を分割

2. 同時アクセス

  • 制限: 30回/秒
  • 対策: キャッシュ活用

3. データサイズ

  • 制限: 500万セル
  • 対策: 古いデータをアーカイブ

セキュリティ

1. 認証の追加

function doGet(e) {
  const apiKey = e.parameter.apiKey;
  const validKey = PropertiesService.getScriptProperties().getProperty('API_KEY');
  
  if (apiKey !== validKey) {
    return ContentService.createTextOutput(
      JSON.stringify({ error: 'Unauthorized' })
    ).setMimeType(ContentService.MimeType.JSON);
  }
  
  // 通常の処理
}

2. CORS対応

function doGet(e) {
  const output = /* your logic */;
  
  return output
    .setMimeType(ContentService.MimeType.JSON)
    .setHeader('Access-Control-Allow-Origin', '*');
}

まとめ

10分でできたこと:

  • ✅ SpreadsheetをREST API化
  • ✅ CRUD操作実装
  • ✅ 0円で運用

活用例:

  • ToDoアプリ
  • 簡易CMS
  • フォーム送信先
  • データ収集システム

コスト:

  • 開発: 10分
  • 運用: 0円/月

次のステップ:

  • 認証追加
  • バリデーション
  • エラーハンドリング強化

参考リンク

著者について

横浜でSEOコンサルタント・データアナリストをしています。

最近のプロジェクト:

  • Tokyo VPN Speed Monitor - Google Apps Scriptで自動測定システム構築、運用コスト0円で稼働率99.8%達成

Twitter: @takechiyo0210

GitHub: https://github.com/hmy0210/vpn-stability-ranking

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?