1. 概要
本ドキュメントは、Node.js + Express / 純粋な HTML・CSS・JavaScript で構成された「汎用予約カレンダー」パッケージの導入と運用のための手引きです。 フリーランスや受託開発者の方向けに、セットアップからセキュリティ、日々の運用までを一冊で把握できるようにまとめています。
BYO-Auth(Bring Your Own Authentication):本パッケージには管理者用の認証・ログインは同梱していません。
導入者側の要件に合わせて、Firebase / Supabase / Auth0 / Cognito / 逆プロキシ(Basic+IP制限)等で必ず保護してください。
導入者側の要件に合わせて、Firebase / Supabase / Auth0 / Cognito / 逆プロキシ(Basic+IP制限)等で必ず保護してください。
2. 動作環境・前提
- Node.js 18 以上(LTS 推奨)
- MySQL 8 系(互換: MariaDB 10.5+)
- サーバー: Linux か macOS を推奨(Windows でも動作可)
- フロント: 純粋な HTML / CSS / JavaScript(ビルドなし)
- SMTP アカウント(予約通知・エラー通知)
3. 機能ハイライト
- 即予約(◯)/ 仮予約(△)/ TEL の自動判定(人数・組数・しきい値)
- 2歳未満のカウント有無、電話番号必須/任意の切替
- 曜日別に最大3パターンの営業時間、定休日/臨時休業/特定日TELのみをワンクリック切替
- 予約番号から人数/日時変更・キャンセル(お客様&管理画面)
- OK/NG メール文の編集、キャンセル/違約金ポリシー編集
- CSV 出力(顧客・予約)とデータ初期化、埋め込み用 iframe
- 多店舗対応(店舗ごとの差異は最小修正で吸収)
4. セットアップ手順
- MySQL に sample_calendar データベースを作成し、付属 SQL を流します(全テーブル作成)。
- リポジトリを配置し、依存をインストール:
$ npm i express helmet express-rate-limit mysql2 async-retry express-validator nodemailer xss multer cors node-cron cron dotenv .envを作成し、MySQL / SMTP / 暗号鍵(ENCRYPTION_KEY)を設定。- 必要に応じて
public/配下にロゴ等の静的ファイルを配置。 - 起動(例):
$ npm start # or node server.js - ブラウザで管理画面と公開画面を確認し、shops / shop_settings に初期データを登録。
本番運用では HTTPS・プロセス管理(PM2等)・ログ収集・WAF/ACL を推奨。
5. .env 設定例
| キー | 説明 |
|---|---|
DB_HOST / DB_USER / DB_PASS / DB_NAME |
MySQL 接続。DB_NAMEは例:sample_calendar |
PORT |
アプリの待受ポート(例:3000) |
EMAIL_HOST / EMAIL_USER / EMAIL_PASS /
EMAIL_FROM_NAME |
SMTP(TLS/465使用)。通知・テンプレに利用 |
ENCRYPTION_KEY |
32バイト鍵(16進64桁)。顧客氏名/電話/メールのAES-256-GCM暗号化に使用 |
ADMIN_PURGE_PASSWORD |
データ初期化API用パス(/notices/purge-reservations)。強固な値必須 |
NODE_ENV |
production で最適化 |
openssl rand -hex 32
6. データベース概要
主なテーブルのみ簡潔に。詳細スキーマは付属 SQL を参照してください。
| テーブル | 用途 |
|---|---|
shops |
店舗基本情報(名称・連絡先・ロゴURL 等) |
shop_settings |
予約ポリシー(最大予約月数、人数/組数しきい値、TEL切替、幼児カウント 等) |
shop_time_slots |
曜日別の営業時間(最大3区間、15分刻み) |
shop_regular_holidays |
第1〜第5など週番号付き定休日 |
shop_special_days |
臨時休業・臨時営業など例外日 |
reservation_slots |
日付×時間スロット。開放状態/満席/TEL等のステータスを管理 |
reservations |
予約本体(暗号化済み氏名/電話/メール、人数、ステータス等) |
customers |
顧客マスタ(暗号化フィールド・ハッシュ重複防止) |
shop_notices |
キャンセル/違約金/案内文などの可変コンテンツ |
seats(任意) |
座席/個室の定義(将来の座席指定に備えた拡張) |
reservation_logs(任意) |
予約操作ログ(生成/確定/取消/更新 等) |
notification_logs(任意) |
通知履歴(誰に何をいつ送ったか) |
restaurants(任意) |
将来拡張向けの外部連携プレースホルダ |
暗号化カラムは
VARBINARY とし、検索には SHA-256 ハッシュ列を併用(重複検知・同一顧客判定の補助)。6A. ○/△/TEL/満 の判定ロジック
管理画面の 平均滞在時間(minutes)、バッファ時間(minutes)、アラート閾値(人数/組数)、上限(max_seats/max_groups) を用いて、スロットの状態(◯/△/TEL/満)を自動判定します。
6A-1. 基本の考え方(ウィンドウ方式)
- 予約が入る時刻を中心に、± 平均滞在時間の「滞在ウィンドウ」を作ります。
- このウィンドウ内に重なっている予約の 人数合計 と 組数 を集計します(幼児を人数に含めるかは
count_infant設定)。 - 集計結果が アラート閾値(人数 or 組数)以上なら、そのウィンドウにかかるスロットを △(request) にします。
- さらに 上限値(max_seats or max_groups)以上なら、そのウィンドウにかかるスロットは 満(full) になります。
優先度:満(full) > TEL(tel) > △(request) > ◯(open)
複数の予約ウィンドウが重なる場合は、上記の優先度でステータスが決まります。
6A-2. 満席時の「周辺スロット」をTELにする(バッファ)
あるウィンドウが 満(full) になった場合、そのウィンドウの前後に「バッファ(分)」を足した範囲のスロットを TEL(tel) に自動変更できます。
- 例)平均滞在 60分、バッファ 15分 → 「±60分」の満ウィンドウの外側 ±15分を TEL に
- 目的:満席直前/直後の時間に電話誘導し、運用の裁量で受け入れる余地を確保
6A-3. 用語と設定の対応
| 概念 | 設定項目(shop_settings) | 説明 |
|---|---|---|
| 平均滞在時間 | average_stay_minutes |
ウィンドウの半径。大きいほど「重なり」やすく、△/満が出やすい |
| バッファ | buffer_minutes |
満ウィンドウの前後に追加してTELへ誘導する幅 |
| 人数アラート閾値 | alert_people_threshold |
ここを超えたら △(request) |
| 組数アラート閾値 | alert_groups_threshold |
ここを超えたら △(request) |
| 人数ハード上限 | max_seats |
ここを超えると 満(full) |
| 組数ハード上限 | max_groups |
ここを超えると 満(full) |
| 幼児カウント | count_infant |
幼児(2歳未満)を人数合計に含めるか |
6A-4. チューニングのコツ
- △の出し方:上限(max_)より少し手前にアラート閾値を置くと、満席の前段階で△になり、過剰予約の抑制や電話誘導がしやすくなります。
- バッファ:回転に余裕を持たせたい場合は 15〜30分を目安に。ゼロにすると「満→TEL」の幅がなくなります。
- 滞在時間:カフェ/ランチは 45〜90分、ディナーは 60〜120分が目安。実態に合わせて随時見直してください。
6A-5. 参考(内部の考え方・擬似式)
# 予約の中心時刻を T(分)とすると
window_start = T - average_stay_minutes
window_end = T + average_stay_minutes
people_in_window = Σ (大人 + 小人 + [count_infantなら幼児])
groups_in_window = 件数
if (max_seats > 0 and people_in_window >= max_seats) or
(max_groups > 0 and groups_in_window >= max_groups):
status = "full"
elif (alert_people_threshold > 0 and people_in_window >= alert_people_threshold) or
(alert_groups_threshold > 0 and groups_in_window >= alert_groups_threshold):
status = "request"
else:
status = "open"
# fullのときはバッファでTEL範囲を追加
tel_zone = (window_start - buffer_minutes, window_start) ∪
(window_end, window_end + buffer_minutes)
複数予約が重なる場合は「満 > TEL > △ > ◯」の優先度でマージします。
7. セキュリティ実装の要点
- 暗号化 / 復号: AES-256-GCM(IV 12B + AuthTag 16B)で氏名/電話/メール等を VARBINARY に保存。鍵は
ENCRYPTION_KEY(32B hex)。 - ハッシュ: SHA-256 による重複検知(
email_hash,phone_hash)。 - HTTP ヘッダ強化:
helmet()を適用(CSP は案件に応じて調整)。 - レート制限:
express-rate-limitにより予約・検索等を制御(例:1分7回)。 - XSS 対策: 表示時に
xssで許可タグ限定サニタイズ(href/srcのスキーム制限、on*禁止、rel="noopener noreferrer nofollow"強制)。 - 入力検証:
express-validatorでサーバ側バリデーション(人数上限、フォーマット、範囲チェック)。 - ファイルアップロード:
multerによる MIME/サイズ制限(PNG/JPEG/GIF・1MB・拡張子は MIME から決定)。 - メール送信:
nodemailer+ リトライ / 障害通知(sendMailAsyncWithRetry,sendErrorNotification)。 - トランザクション: 予約変更/キャンセルで一貫性を担保、周辺スロットを自動再計算。
- CSV: UTF-8+BOM、ダブルクォートエスケープで Excel 互換。復号失敗時は安全に空文字化。
- 管理系保護: 管理ルートは必ず認証・アクセス制限(Basic+IP / IDaaS / リバースプロキシ)。
- 監査:
reservation_logs/notification_logsを有効化して追跡可能に(任意)。
8. 運用・日常オペレーション
営業時間・定休日の設定
- 曜日ごとに最大3区間(15分刻み)で設定(
shop_time_slots)。 - 第1〜第5の週番号付き定休日に対応(
shop_regular_holidays)。 - 臨時休業/臨時営業は例外日として登録(
shop_special_days)。 - 特定日を「TELのみ」にする/元に戻す設定もワンクリック(
/notices/set-tel-day)。
予約の取り扱い
- お客様は予約番号で 人数/日時変更・キャンセル が可能(
/clmaster/*)。 - 管理画面からも同様の操作(変更に伴い該当日のスロット状態を自動再計算)。
- 仮予約への OK/NG メールはワンクリック。文面は管理画面で編集可。
データの入出力
- 顧客/予約 CSV を期間指定でエクスポート。
- 初期化(削除)操作は慎重に。成功時は管理者へ通知メールが送信されます。
9. 拡張アイデア
- Google カレンダー API 連携(予約生成/変更の同期)
- 管理画面からカレンダー配色テーマを変更
seatsテーブル連動による座席指定・個室確約- Webhooks による外部在庫/POS 連携、Zapier/Make 連携
- リマインダー SMS/LINE 通知(
notification_logsで監査)
10. トラブルシュート
| 症状 | 確認ポイント |
|---|---|
| 予約が確定しない / 満席扱いになる | しきい値(max_seats/max_groups)や滞在時間/バッファ、幼児カウント設定を確認。 |
| CSV が文字化けする | Excel での読み込み時に UTF-8 を選択。BOM 付き出力なので通常はそのまま開けます。 |
| 画像ロゴが反映されない | MIME が PNG/JPEG/GIF か、1MB 以下かを確認(multerの制限)。 |
| メールが届かない | SMTP 設定、送信ドメイン認証(SPF/DKIM/DMARC)、迷惑メール判定を確認。 |
| 時刻ずれが起きる | サーバ/DB のタイムゾーン設定(DBは Asia/Tokyo を目安)。 |
| iframe の高さが合わない | postMessage の導線と origin 検証を再確認。 |
11. FAQ
Q. フロントはフレームワーク不要ですか?
はい。純粋な HTML / CSS / JS で動作します。必要に応じて任意のフレームワークに差し替え可能です。
Q. 多店舗運用時の注意は?
営業時間や定休日の差異がある場合、shop_settings と各種マスタで店舗単位の設定を維持してください。
Q. 個別要件の実装は難しい?
ベース機能は揃っており、AI を活用すれば「色替え」「外部連携」などの改修は比較的容易です。
12. 既存サイトへの埋め込み(iframe)
基本スニペット(origin検証あり)
<div style="max-width:680px;margin:0 auto;">
<iframe
id="reserveFrame"
src="https://あなたのドメイン名/reserve-calendar.html?shop_id=1&embed=1&parent_origin=https%3A%2F%2F埋め込み先のドメイン"
title="予約カレンダー"
style="border:0;width:100%;height:720px;display:block;background:transparent"
loading="lazy"
referrerpolicy="no-referrer-when-downgrade"
></iframe>
</div>
<script>
(function () {
const frame = document.getElementById('reserveFrame');
const ALLOWED_ORIGIN = 'https://あなたのドメイン名'; // ← カレンダー配信元の正しいオリジンに固定
const MIN_H = 320, MAX_H = 5000;
let rafId = null;
window.addEventListener('message', function (e) {
// セキュリティ:想定オリジンのみ許可
if (e.origin !== ALLOWED_ORIGIN) return;
if (!e.data || e.data.type !== 'setIframeHeight') return;
const h = Math.max(MIN_H, Math.min(parseInt(e.data.height, 10) || 0, MAX_H));
if (rafId) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(() => { frame.style.height = h + 'px'; });
});
// 任意:ロード後に子側へ「初期化」通知(子が高さを計測して送り返す設計)
frame.addEventListener('load', () => {
try { frame.contentWindow?.postMessage({ type: 'reserve:init' }, ALLOWED_ORIGIN); } catch (_) {}
});
})();
</script>
埋め込み先(子)からの送信(同梱フロントで対応済)
// 親へ高さを通知
window.parent.postMessage({ type:'setIframeHeight', height: document.body.scrollHeight }, '*');
公開サイト側は
postMessage の origin を必ず検証してください。13. 主なAPI(抜粋)
管理系(要認証・要アクセス制限)
POST /notices/set-tel-day当日/特定日をTELのみ⇔元に戻すGET /notices/shop-info/POST /notices/shop-info店舗名・ロゴ・案内文などGET /notices/shop-notices/:shop_id/POST ...キャンセル/違約金ポリシー等GET /notices/shop-settings/:shop_id/POST ...予約ルール・上限・しきい値GET /notices/shop-time-slots/:shop_id/POST ...営業時間(曜日×最大3区間)GET /notices/shop-regular-holidays/:shop_id/POST ...第n週定休日POST /notices/purge-reservations予約/枠の初期化(メール通知あり)POST /clmaster/modify-people人数変更(一貫性検証・周辺再計算)POST /clmaster/modify-datetime日時変更(不足枠の自動生成)POST /clmaster/cancel予約キャンセルGET /clmaster/get-reservations管理カレンダー用予約一覧
公開系(一般利用)
GET /clmaster/lookup?id=...予約番号からの照会(部分復号)GET /clmaster/get-regular-holidays?shop_id=...定休日ルール取得GET /clmaster/customers-csv/GET /clmaster/reservations-csv(※管理用途・公開不要)
エンドポイントの公開/非公開は導入構成により変化します。公開が必要な最小限のみ外出しし、他は認証の背後へ。
14. CSV出力
顧客・予約ともに UTF-8 + BOM、全セルをダブルクォートで出力。Excel/スプレッドシートでの文字化け・数式化を防ぎます。
GET /clmaster/customers-csv?shop_id=1GET /clmaster/reservations-csv?shop_id=1&date_from=YYYY-MM-DD&date_to=YYYY-MM-DD
CSVエンドポイントは管理用途です。公開しないでください。
15. 多店舗運用のポイント
- shop_id 単位で営業時間/定休日/特別日/設定を保持。
- 店舗ごとに営業時間が大きく異なる場合は UI やルールの微調整を想定。
- 座席指定(
seats)やログ(reservation_logs/notification_logs)は任意拡張。
16. ライセンス/サポート
- 商用利用可(クライアント案件・自社サービス)
- ソースの再配布は不可(導入先への提供・運用は OK)
- 簡易サポート:初期セットアップのポイント解説 / Q&A(軽微なカスタム相談可)
© 2025 汎用予約カレンダー(BYO-Auth版)