出席アプリを作ってデータをGoogle Spreadsheetsに保存する

お断り
js 初心者なので,何やってるのかあまり理解できていないです.ご容赦ください.
どんなアプリ?
出席アプリを作って,名前とタイムスタンプをGoogle Spreadsheetに保存する方法をChatGPT+αで作成したので,記録としてここに残しておく.
名前は自由入力ではなく,選択式.表記ゆれを防ぐため.
ファイル構造
index.html
index.js
package.json
credentials.json
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QR Code Scanner</title>
<script src="https://unpkg.com/html5-qrcode" type="text/javascript"></script>
</head>
<body>
<div id="reader" style="width: 300px;"></div>
<select id="nameSelect">
<option value="John">John</option>
<option value="Jane">Jane</option>
<option value="Doe">Doe</option>
</select>
<button onclick="submitData()">Submit</button>
<script>
function onScanSuccess(decodedText) {
console.log(`Scanned result: ${decodedText}`);
// Optional: You can use decodedText for additional logic
}
function onScanFailure(error) {
console.warn(`QR scan failed: ${error}`);
}
const html5QrcodeScanner = new Html5QrcodeScanner(
"reader", { fps: 10, qrbox: 250 });
html5QrcodeScanner.render(onScanSuccess, onScanFailure);
async function submitData() {
const name = document.getElementById("nameSelect").value;
const timestamp = new Date().toISOString();
const response = await fetch('/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, timestamp })
});
const result = await response.json();
alert(result.message);
}
</script>
</body>
</html>
index.js
const express = require('express');
const { google } = require('googleapis');
const bodyParser = require('body-parser');
const app = express();
const path = require('path');
// Serve the index.html file on the root path
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.use(bodyParser.json());
require('dotenv').config();
const credentials = require(process.env.GOOGLE_CREDENTIALS);
// const credentials = require('./credentials.json'); // Service account credentials
const spreadsheetId = 'sheet-id'; // Replace with your spreadsheet ID
async function appendToSheet(data) {
const auth = new google.auth.GoogleAuth({
credentials,
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});
const sheets = google.sheets({ version: 'v4', auth });
const response = await sheets.spreadsheets.values.append({
spreadsheetId,
range: 'Sheet1',
valueInputOption: 'RAW',
resource: {
values: [data],
},
});
return response;
}
app.post('/submit', async (req, res) => {
const { name, timestamp } = req.body;
if (!name || !timestamp) {
return res.status(400).json({ message: 'Name and timestamp are required' });
}
try {
await appendToSheet([timestamp, name]);
res.json({ message: 'Data saved successfully!' });
} catch (error) {
res.status(500).json({ message: 'Error saving data', error: error.message });
}
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
package.json
{
"name": "qr-code-scanner-service",
"version": "1.0.0",
"description": "A simple QR code scanning service that saves data to Google Sheets",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"body-parser": "^1.20.1",
"dotenv": "^16.4.7",
"express": "^4.18.2",
"googleapis": "^105.0.0"
},
"devDependencies": {
"nodemon": "^3.1.9"
},
"keywords": [
"qr-code",
"google-sheets",
"express",
"nodejs"
],
"author": "Your Name",
"license": "MIT"
}

Google Sheets API
- Google Could Platform (旧名: Google Cloud Console)へアクセス.
- 新しいプロジェクトを作成.
-
Google Cloud
>APIs & Services
>Enable APIs and Services
>Sheet
と検索 > Google Sheets APIをEnable. -
Google Cloud
>IAM & Admin
>Service Accounts
>+ create service account
> service account name を適当な名前にすると,service account ID を勝手に埋めてくれる >done
. - service account がGoogle Spreadsheetを編集できるようにする:
IAM & Admin
>IAM
>grant access
> New principalsにservice accountのメアドを指定.Assign roles でEditor
入力. >save
. - credentialを取得:
Google Cloud
>IAM & Admin
>Service Accounts
> 使用するservice accountをクリック >keys
>add key
>create new key
>json
>create
- 保存したcredential (XXX.json) を
credentials.json
へと名前変更し,プロジェクトのディレクトリへ移す.
※principalが全然わからなかったのですが,ざっというとアカウントのことでした.APIを使うときにservice accountを使うので,それのことです.ちなみに,個人のアカウントでプロジェクトを作成すると,自分のアカウントがownerロールをもったprincipalです.参照:https://cloud.google.com/iam/docs/overview
Google Spreadsheetsにて
- 出力したいSpreadsheetsにアクセス
- Spreadsheetsのid を取る:URLのhttps://docs.google.com/spreadsheets/d/{ここがID}/edit?gid=0#gid=0XXXXXXXX
- 共有 > service accountをeditor権限で追加
localでdeploy (win/powershell)
※nodeは入れておく
npm install
npm install dotnev
$env:GOOGLE_CREDENTIALS = "C:\path\to\credentials.json"
node index.js
※環境変数の確認: $env:GOOGLE_CREDENTIALS

結果
localhost:3000にアクセスすると,名前とタイムスタンプをGoogle Spreadsheets に保存できた.
QRコードを読み込むと,出席を報告できる画面に遷移するというUIを想定してChatGPTにプロンプトしたので,QRコードを読み込めるけど何もしない謎機能が入ってしまった.
可能性1
QRリーダーの使い道として,毎回キーを作成して,それをQRコードとして表示して,読み込む,その情報もGoogleSpreadsheetsへ保存し,キーが正解キーと違う場合は欠席扱いにする.e.g)毎ゼミ,キーを作成: zemi-12r4t6z!
.これをQRコードにして,毎回ゼミ前に画面に表示.各位はそれをスキャンして出席報告
問題
- 毎回違うキーを作成しないといけない.予測できそうなキーだと不正される e.g.) 'zemi-1215' など日付と簡単な規則
- 毎回検証しないといけない:これはそんなに大きな負担じゃないかも.簡単なスクリプトでできそう
- 出席率の共有がしづらいかも:各々がどれだけ出席したのかの可視化に別の機構が必要になる
可能性2
QRコードをスキャンして,特定のキーであれば名前選択&送信ができるようになる.
-> 可能性1課題2は解決できる.
問題
- 現状の構造だと,htmlから直接backendに情報を送信しているので,そこに検証を挟む必要がある.
- 可能性1にも共通する問題だが,この出席確認アプリにアクセスするためにQRコードを使用すると思うので,キーをこのアプリのURLにするのが現実的,だとすると,キーが固定になっちゃう?
- ->URLのパラメータにキーをいれて,キーをURLから抽出するのはどうか?(可能性1 課題1)