はじめに
「簡単なデータベースが欲しいけど、サーバー立てるのは面倒…」
そんなとき、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