開発

予約システムを自作する:PostgreSQLの排他制約で二重予約を止める実装手順【18系】

予約システムを自作する:PostgreSQLの排他制約で二重予約を止める実装手順【18系】

予約システムを自作するとき、最初に詰まるのは画面ではありません。同じ枠に2人が同時に申し込んだ瞬間の処理です。この記事では、PostgreSQL 18系の排他制約(EXCLUDE制約)で二重予約をデータベース側で止める方法を、テーブル定義からPythonの登録処理、同時送信のテストまでコード付きで示します。決済待ちの仮予約の期限、キャンセル規定と手数料の持ち方、確認メールと前日リマインドの送信待ち表も、作る順に沿って解説する構成です。最後に、自作を続けるか、既製サービスや受託開発に切り替えるかの基準を、予約の要件と連携先の数で示します。

まとめ:予約システムの自作は排他制約から作り、仮予約・キャンセル・通知の順に足す

自作で最初に作るのは予約フォームではなく、予約テーブルと排他制約です。PostgreSQLなら「同じリソースで時間帯が重なる2行」を禁じる制約を1つ書くだけで、同じ枠への2件目の登録をデータベースが拒否します。アプリ側のif文で空きを確かめる方法は、同時アクセスに対しては防御になりません。

その上に、決済待ちの仮予約と期限切れの解放、予約時点のキャンセル規定の保存、確認とリマインドの送信待ち表を順に足します。この4段がそろえば、来店予約や会議室予約の最小構成として運用に乗せられる形です。

自作が向くのは、予約の単位が既製サービスの枠設定に収まらず、決済と通知以外の外部連携が1本以内に収まる場合に限られます。決済・LINE・顧客管理・基幹システムのうち3本以上とつながると、自作では保守の負担が重くなりがちです。開発方式の選び方や技術選定、外注時の費用相場は予約システム開発の進め方|自作の設計・技術選定・必須機能と費用を解説で整理しているので、本稿は実装に絞ります。

自作する前に決める予約の型、時間帯を占める予約か定員制の枠かで表の形が変わる

予約の表の形は、1つの枠を1件の予約が占めるのか、1つの枠に定員まで複数の予約が入るのかで分かれます。排他の書き方もここで変わるため、コードより先に決めておきます。

美容室や会議室の時間帯予約と、レッスンの定員制予約で異なるテーブル構成

型ごとの持ち方と、二重予約の止め方を表にまとめます。

予約の型 例 予約の持ち方 二重予約の止め方
時間帯を占める型 美容室の担当者・会議室 予約ごとに開始と終了 排他制約で重なりを禁止
定員制の枠 レッスン・説明会・ツアー 枠の表に定員と残数 残数を1文のUPDATEで減らす
両方の混在 講師指名つきの教室 時間帯と枠の両方 2つの制約を同じ取引で確認

本稿のコードは、時間帯を占める型です。所要時間がメニューごとに30分・60分・90分と変わる予約は、15分刻みの枠を複数行押さえるより、開始と終了を1つの範囲型の列で持つ方が判定が単純になります。定員制の残数を1文のUPDATEで減らす実装は、WordPressで予約システムを作る:プラグイン比較とカスタム投稿型・排他制御の実装でMySQLのコードとして示しました。

画面側の空き確認だけでは二重予約が起きる、同時に押された瞬間の処理順

典型的な失敗は、空いているかをSELECTで確かめてからINSERTする処理をアプリに書くことです。PostgreSQLの既定の分離レベルはRead Committedで、SELECTは文の開始前に確定したデータしか見ません(公式ドキュメントのトランザクション分離の章)。同時に2件が来ると、次の順で処理が進みます。

  1. 客Aの処理がSELECTを実行し、10時の枠が空いていると判断する
  2. 客Bの処理も同じSELECTを実行する。Aの登録はまだ確定前なので、やはり空いていると判断する
  3. AがINSERTして確定する
  4. BもINSERTして確定し、10時の枠に2件が入る

2件の間隔が数ミリ秒でも起こります。人気枠の受付開始直後や、予約ボタンの二度押しが典型です。対策は、重なりの判定そのものをデータベースの制約に任せること。制約はすべての取引に同じ規則で効くので、管理画面からの手入力や外部APIからの登録でも同じように止まります。

検証環境の準備、DockerでPostgreSQL 18を起動して拡張を入れる

検証にはPostgreSQLの公式Dockerイメージを使います。PostgreSQLのバージョン方針のページでは、2026年10月時点でサポート中の最新版は18.6で、19はリリース候補(RC)の段階です。本稿のSQLは18系で書いていますが、範囲型と排他制約はそれより前の版から使えます。

コンテナの起動と拡張の有効化、Pythonドライバpsycopg 3.3系の導入コマンド

次のコマンドで、データベースの起動、拡張の有効化、Pythonのライブラリの導入までを済ませます。

docker run -d --name yoyaku-db \
  -e POSTGRES_USER=app -e POSTGRES_PASSWORD=app -e POSTGRES_DB=yoyaku \
  -p 5432:5432 postgres:18

docker exec -it yoyaku-db psql -U app -d yoyaku \
  -c "CREATE EXTENSION IF NOT EXISTS btree_gist;"

python -m pip install "psycopg[binary]==3.3.6" tzdata

btree_gistは、整数や文字列のような通常の型をGiSTインデックスで扱えるようにする拡張です。リソースIDの「等しい」と時間帯の「重なる」を1つの排他制約にまとめるために使います。btree_gistの公式の説明では信頼済み(trusted)の拡張とされ、データベースにCREATE権限があれば、スーパーユーザーでなくても有効にできます。

ドライバは、2026年10月時点の最新が3.3.6のpsycopgです。psycopgのインストール手順では、Python 3.10〜3.15とPostgreSQL 10〜18が公式の対応範囲とされています。tzdataは、Windowsでタイムゾーンを扱うために入れます(理由は通知の章で述べます)。

リソース・予約・通知待ちの3表を作るDDLと、排他制約に状態の条件を付ける書き方

次のSQLをschema.sqlとして保存し、docker exec -i yoyaku-db psql -U app -d yoyaku < schema.sqlで流し込みます。

CREATE TABLE resource (
  id   bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  name text NOT NULL
);

CREATE TABLE booking (
  id            bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  resource_id   bigint NOT NULL REFERENCES resource (id),
  during        tstzrange NOT NULL CHECK (NOT isempty(during)),
  status        text NOT NULL
                CHECK (status IN ('held', 'confirmed', 'block', 'cancelled', 'expired')),
  hold_until    timestamptz,
  customer      text,
  price         integer NOT NULL DEFAULT 0,
  cancel_policy jsonb,
  created_at    timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT booking_no_overlap
    EXCLUDE USING gist (resource_id WITH =, during WITH &&)
    WHERE (status IN ('held', 'confirmed', 'block'))
);

CREATE TABLE notification_outbox (
  id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  booking_id bigint NOT NULL REFERENCES booking (id),
  kind       text NOT NULL CHECK (kind IN ('confirm', 'remind')),
  send_at    timestamptz NOT NULL,
  sent_at    timestamptz
);
CREATE INDEX outbox_due ON notification_outbox (send_at) WHERE sent_at IS NULL;

INSERT INTO resource (name) VALUES ('個室1'), ('個室2');

二重予約を止めているのは、booking_no_overlapの3行です。resource_id WITH =とduring WITH &&を並べると、同じリソースで時間帯が重なる2行の共存を禁じます。範囲型の章にある会議室予約の例と同じ形で、違反したINSERTはエラーで拒否されます。

末尾のWHEREは、仮予約・確定・休業枠の3つの状態だけを重なり判定の対象にする条件です。CREATE TABLEの説明のとおり、条件付きの排他制約は内部で部分インデックスとして作られます。取消済みと期限切れの行は判定から外れるので、キャンセルされた枠にはすぐ次の予約を入れられます。

時間帯をtstzrange(開始, 終了)で作ると、開始を含み終了を含まない範囲になります。10時〜11時の予約と11時〜12時の予約は重ならないため、連続した枠を隙間なく販売できる仕組みです。休業や臨時の貸切は、statusをblockにした行として入れておけば、予約と同じ制約で止まります。

予約を受け付けるPythonコード、仮予約の登録から確定までをトランザクションで書く

登録の処理はyoyaku.pyにまとめます。下のコードに含めた処理は、仮予約の登録、確定、リマインド時刻の計算です。キャンセルと通知の送信は、後の章で同じファイルに足していきます。

排他制約の違反コード23P01を受けて「この枠は埋まりました」を返す登録処理

from datetime import datetime, time, timedelta
from zoneinfo import ZoneInfo

import psycopg
from psycopg import errors
from psycopg.types.json import Jsonb

JST = ZoneInfo("Asia/Tokyo")
DSN = "host=localhost dbname=yoyaku user=app password=app"

# 開始の何時間前までの取消なら何割か(例。上から順に判定する)
CANCEL_POLICY = [
    {"hours_before": 48, "rate": 0.0},
    {"hours_before": 24, "rate": 0.3},
    {"hours_before": 0, "rate": 0.5},
]


class SlotTaken(Exception):
    pass


def hold_booking(conn, resource_id, start, minutes, customer, price, hold_minutes=15):
    """枠を仮押さえして予約IDを返す。埋まっていれば SlotTaken。"""
    end = start + timedelta(minutes=minutes)
    try:
        with conn.transaction():
            # 期限切れの仮予約を先に外す(排他制約の WHERE に now() は書けない)
            conn.execute(
                """UPDATE booking SET status = 'expired'
                   WHERE resource_id = %s AND status = 'held'
                     AND hold_until < now()
                     AND during && tstzrange(%s, %s)""",
                (resource_id, start, end),
            )
            row = conn.execute(
                """INSERT INTO booking
                     (resource_id, during, status, hold_until, customer, price)
                   VALUES (%s, tstzrange(%s, %s), 'held',
                           now() + make_interval(mins => %s), %s, %s)
                   RETURNING id""",
                (resource_id, start, end, hold_minutes, customer, price),
            ).fetchone()
            return row[0]
    except errors.ExclusionViolation:
        raise SlotTaken(f"{start:%m月%d日 %H:%M}からの枠は埋まりました") from None


def reminder_time(start):
    """開始日の前日18時(日本時間)。"""
    day_before = start.astimezone(JST).date() - timedelta(days=1)
    return datetime.combine(day_before, time(18, 0), tzinfo=JST)


def confirm_booking(conn, booking_id):
    """決済の完了後に呼ぶ。仮予約の期限が切れていれば SlotTaken。"""
    with conn.transaction():
        row = conn.execute(
            """UPDATE booking
               SET status = 'confirmed', hold_until = NULL, cancel_policy = %s
               WHERE id = %s AND status = 'held' AND hold_until >= now()
               RETURNING lower(during)""",
            (Jsonb(CANCEL_POLICY), booking_id),
        ).fetchone()
        if row is None:
            raise SlotTaken("仮予約の期限が切れました。もう一度枠を選んでください")
        conn.execute(
            "INSERT INTO notification_outbox (booking_id, kind, send_at)"
            " VALUES (%s, 'confirm', now())",
            (booking_id,),
        )
        remind_at = reminder_time(row[0])
        if remind_at > datetime.now(JST):
            conn.execute(
                "INSERT INTO notification_outbox (booking_id, kind, send_at)"
                " VALUES (%s, 'remind', %s)",
                (booking_id, remind_at),
            )

重なりを判定する行は、コードのどこにもありません。INSERTが排他制約に触れると、PostgreSQLはSQLSTATE 23P01(エラーコード一覧での名前はexclusion_violation)を返します。psycopgはこれをExclusionViolation例外として送出するので、登録関数はそれを受けて利用者向けの文言に置き換えるだけです。

2件が同時に来た場合、後のINSERTは先の取引の結果を待ちます。先が確定すれば違反になり、先が取り消されれば後の登録がそのまま通ります。psycopgのトランザクションの説明のとおり、with conn.transaction()の中で例外が出ると取引は取り消されるため、半端な行は残りません。

決済待ちの仮予約に15分の期限を持たせ、期限切れの枠を登録の直前に解放する

事前決済を入れると、枠を押さえてから決済が終わるまでの数分間が問題になります。決済画面で離脱した客の枠が押さえられたまま残ると、空いているはずの枠が売れません。そこでhold_bookingは、statusをheld、hold_untilを15分後にして登録します。決済完了の通知を受けたらconfirm_bookingでconfirmedに変える流れです。confirm_bookingは期限内の仮予約しか確定しないため、期限を過ぎてから決済が完了した客は返金の処理に回します。

期限切れの判定を排他制約のWHEREに書けば済みそうに見えますが、書けません。CREATE INDEXの説明には、インデックスの定義に使える関数は結果が引数だけで決まるものに限られ、現在時刻のような外の要因で変わるものは使えないとあります。そこで、登録の直前に同じ時間帯の期限切れの仮予約をexpiredに変えてから、INSERTする手順です。この方法なら、定期的な掃除の処理が止まっていても枠は解放されます。

2本のスレッドで同じ枠を同時に取り、1件だけ成功するかを確かめるテスト

排他が効いているかは、同じ枠へ2件を同時に送って確かめます。threading.Barrierで2本のスレッドの開始をそろえ、別々の接続から同じ10時の枠を取りに行かせます。

import threading
from datetime import datetime

import psycopg

from yoyaku import DSN, JST, SlotTaken, hold_booking

start = datetime(2026, 11, 20, 10, 0, tzinfo=JST)
gate = threading.Barrier(2)
results = []


def try_book(name):
    with psycopg.connect(DSN) as conn:
        gate.wait()  # 2人が同時に「予約する」を押した状態を作る
        try:
            booking_id = hold_booking(conn, 1, start, 60, name, 5000)
            results.append((name, "成功", booking_id))
        except SlotTaken as e:
            results.append((name, "失敗", str(e)))


threads = [threading.Thread(target=try_book, args=(n,)) for n in ("客A", "客B")]
for t in threads:
    t.start()
for t in threads:
    t.join()

for r in results:
    print(r)
assert sorted(r[1] for r in results) == ["失敗", "成功"]

期待する出力は「成功」と「失敗」が1件ずつで、どちらが成功するかは実行のたびに変わります。2件とも成功したら、排他制約が作られていないか、WHEREの状態の条件が登録時のstatusと合っていません。同じ枠は15分間仮予約で埋まるので、続けて試すときはstartの時刻を変えてください。このテストはCIに入れ、スキーマを変えるたびに流すようにします。

キャンセル規定の実装、受付期限と手数料率を予約時点の規定として予約行に残す

キャンセル自体はstatusをcancelledに変える1文で済みます。詰まるのは手数料の計算と、その率の決め方です。

手数料率をJSONで予約に写し、規定を変えても過去の予約の計算がずれない設計

confirm_bookingは、確定の時点でCANCEL_POLICYを予約行のcancel_policy列に写しています。規定を別の表に1つだけ持つと、料金を改定した後に過去の予約を取り消したとき、新しい規定で計算されてしまいます。予約を受けた時点の約束を予約ごとに残せば、改定日をまたいでも計算は変わりません。取消の処理は次のとおりです。

def cancel_booking(conn, booking_id, now=None):
    """確定済みの予約を取り消し、手数料(円)を返す。取り消せない状態なら None。"""
    now = now or datetime.now(JST)
    with conn.transaction():
        row = conn.execute(
            """UPDATE booking SET status = 'cancelled'
               WHERE id = %s AND status = 'confirmed'
               RETURNING lower(during), price, cancel_policy""",
            (booking_id,),
        ).fetchone()
    if row is None:
        return None
    start, price, policy = row  # policy は確定時に写した規定(jsonb は list で返る)
    hours_left = (start - now).total_seconds() / 3600
    for rule in policy:
        if hours_left >= rule["hours_before"]:
            return round(price * rule["rate"])
    return price  # 開始時刻を過ぎてからの取消

CANCEL_POLICYの例は、開始の48時間前までは無料、24時間前までは3割、それより後は5割、開始時刻を過ぎた取消は全額という区分です。率はあくまで例で、実際の値は次のh3で述べる法律の上限を踏まえて決めます。無料で取り消せる締切も同じ規定の区分として持たせておくと、予約画面の表示と手数料の計算が食い違いません。

消費者契約法9条の「平均的な損害」を超える手数料が無効になる範囲と決め方

個人客から受ける予約は消費者契約にあたり、キャンセル料には法律上の上限があります。2026年10月時点のe-Gov法令検索に掲載された消費者契約法の第9条第1項第1号は、解除に伴う損害賠償の予定や違約金の条項について、解除の事由や時期などの区分ごとに、同種の契約の解除で事業者に生ずべき平均的な損害の額を超える部分を無効としています。

条文が時期の区分ごとに平均的な損害と比べる形なので、規定も時期で区分して持つのが実装上の素直な形です。区分ごとに、その時期に取り消された枠が埋め直せずに失う売上や、準備済みの材料費などの実績から率を決めます。同条第2項は、消費者から求められたときに算定の根拠の概要を説明する努力義務を事業者に課しています。率を決めた根拠の数字は社内に残しておき、本番の規定にする前に弁護士などの専門家に確認してください。

確認メールと前日リマインドの通知、送信待ちの表とSKIP LOCKEDで二重送信を防ぐ

通知は、予約の処理の中で直接送らず、送るべき通知を表に積んでから別の処理で送ります。

予約の確定と同じトランザクションで通知待ちを書き、送り漏れを出さない理由

予約を確定したその場でメールを送るコードは、送信に失敗したときに「予約は入ったが通知が届かない」状態を作ります。逆に送信が先で確定が失敗すると、存在しない予約の確認メールが届いてしまいます。confirm_bookingがメールを送らず、notification_outboxに送るべき通知を書くだけにしているのはこのためです。予約の確定と同じ取引なので、両方が残るか、両方が消えるかのどちらかになります。

LINEで通知する場合も、送信先がMessaging APIに変わるだけで同じ表を使えます。LIFFでの予約画面やチャネルの設定は、LINE予約システムを自作する:LIFFとMessaging APIの構成・コード・料金でコード付きで解説しています。

SKIP LOCKEDで複数の送信プロセスに同じ通知を渡さない取り出し方と再送の扱い

def send_due(conn, send, limit=50):
    """送信時刻を過ぎた通知を取り出して送る。cron などで1分ごとに呼ぶ。"""
    with conn.transaction():
        rows = conn.execute(
            """SELECT o.id, o.kind, b.customer, lower(b.during)
               FROM notification_outbox o
               JOIN booking b ON b.id = o.booking_id
               WHERE o.sent_at IS NULL AND o.send_at <= now()
                 AND b.status = 'confirmed'
               ORDER BY o.send_at
               LIMIT %s
               FOR UPDATE OF o SKIP LOCKED""",
            (limit,),
        ).fetchall()
        for outbox_id, kind, customer, start in rows:
            send(kind, customer, start.astimezone(JST))
            conn.execute(
                "UPDATE notification_outbox SET sent_at = now() WHERE id = %s",
                (outbox_id,),
            )

送信の処理を2台で動かすと、同じ行を2台が取り出して二重に送るおそれがあります。FOR UPDATE SKIP LOCKEDは、ほかの取引がロックしている行を待たずに飛ばす指定です。SELECTの説明でも、一般の処理には向かない一方で、複数の取り出し手がキューのような表を扱うときのロック競合を避ける用途に使えるとされています。1台目が取り出した行は取引の終わりまでロックされ、2台目は次の行へ進みます。

残る弱点は、メールを送った直後、sent_atを更新する前に処理が落ちた場合です。このときは取引ごと取り消され、次の回にもう一度送られます。つまり通知は最低1回は届き、まれに2回届く設計です。予約の確認は届かない方が困るので、本稿はこちらを選びました。重複が問題になる送信先なら、通知のIDを一緒に渡して送信側で重複を弾かせます。

前日18時のリマインド時刻をAsia/Tokyoで計算して保存する手順

日時の列は、すべてtimestamptz(タイムゾーン付きの時刻)で持ちます。日付時刻型の章のとおり、PostgreSQLはこの型を内部でUTCとして保存し、取り出すときに接続のタイムゾーンへ変換します。サーバーのタイムゾーン設定が変わっても、保存された時刻はずれません。

reminder_timeは、開始時刻を日本時間に直してから日付を1日戻し、18時を組み立てています。UTCのまま24時間を引くだけだと、朝9時の予約のリマインドは前日の朝9時になり、店の営業前に届く場合があるため注意が必要です。Python標準のzoneinfoモジュールはIANAのタイムゾーンデータを使い、Windowsのようにデータを持たない環境ではtzdataパッケージへの依存を宣言するよう案内されています。予約の時点で前日18時を過ぎていれば、リマインドは作らず確認メールだけを送ります。

自作を続けるか既製サービスか受託開発かを、予約の要件と連携先の数で決める基準

ここまでのコードで、時間帯予約の受付・仮予約・キャンセル・通知の最小構成はそろいました。続けるか切り替えるかは、予約の要件と連携の数で決めます。

自作で進めてよい条件と、作り始める前に止めるべき場面を予約の要件で分ける

自作で進めてよいのは、次の条件をすべて満たす場合です。

  • 予約の単位が既製サービスの枠設定で表せない(機材と担当者を同時に押さえる、所要時間が客ごとに変わる など)
  • PostgreSQLとPythonを保守できる人が社内に1人以上いて、障害の当日中に対応できる
  • 決済と通知以外の外部連携が無いか、1本に収まる

作り始める前に止めるべきなのは、美容室や飲食店の標準的な時間帯予約で、既製サービスの無料〜低額のプランに収まる場合です。このとき自作で得られるのは月額費用の削減だけで、障害の対応や法改正への追従を自社で抱えることになります。既製サービスの料金は、予約システムの料金|5製品の料金比較と課金体系3タイプ、3年総額で見る開発への分岐で3年総額として比べています。

決済・LINE・基幹システムとの連携が増えた段階で受託開発へ切り替える目安

自作の予約システムが重くなるのは、予約そのものより、周りの連携が増えたときです。決済代行・LINE・顧客管理・会計や在庫の基幹システムのうち3本以上とつながると、相手側の仕様変更で予約が止まる経路が増え、テストの組み合わせも膨らみます。複数の拠点でスタッフや機材を共有する、店舗ごとに管理者の権限を分けるといった要件も、たいてい同じ時期に出てきます。管理画面に要る機能は、予約システムの管理者機能|管理画面の要件を5領域に分けて要件書に落とすで整理しました。

この段階で、本稿の排他制約と送信待ちの表を土台に残したまま、開発を外部に任せる選択肢もあります。一創の予約管理システム開発では、自作した部分を残すか作り直すかの判断と、予約データの移し方を含めて相談を受け付けています。

よくある質問

予約システムを自作するときに、実装の段階でよく出る質問をまとめました。

MySQLで予約システムを自作する場合も二重予約は防げますか?

防げますが、方法が変わります。MySQLには時間帯の重なりを禁じる排他制約が無いためです。枠を固定の時間で区切れるなら、「リソースIDと枠の開始時刻」の組に一意制約を張ります。所要時間がメニューごとに変わる予約では、リソースの行をSELECT … FOR UPDATEでロックしてから重なりを確かめ、同じ取引の中で登録します。どの方法でも、本文の同時送信テストで2件目が拒否されることを確かめてから公開してください。

予約システムの自作はノーコードツールやAIのコード生成でもできますか?

画面と登録処理の試作なら短期間で作れます。確認したいのは、出来上がったコードが「空きをSELECTで確かめてからINSERTする」形になっていないかです。この形は同時押しの競合に弱く、1人で試作を触っている間は問題が表に出ません。どの作り方でも、二重予約の判定をデータベースの制約に置いているかを確かめ、本文の同時送信テストを通してから公開します。

予約の日時変更(振替)はどう実装すればよいですか?

予約行のduringを新しい時間帯にUPDATEします。UPDATEでも排他制約は効くので、変更先が埋まっていれば23P01のエラーになり、元の予約はそのまま残ります。取り消してから新しく登録する方法は避けてください。取消と登録の間に元の枠をほかの客に取られ、変更先も埋まっていた場合に、どちらの枠も失うためです。変更に手数料をかけるかどうかは、キャンセル規定とは別に決めておきます。

定休日や臨時休業はどう扱えばよいですか?

休業の時間帯を、statusがblockの予約行として登録します。blockは排他制約の判定に含めてあるので、その時間帯に予約は入りません。定休日のように毎週くり返す休みは、営業カレンダーから向こう数か月分のblock行を定期処理で作っておきます。休業を入れる時間帯に予約が既にあると登録は23P01で失敗するため、先にその客へ連絡して予約を取り消す、という順番が自然に守られます。

予約システムを自作すると費用はどのくらいかかりますか?

本稿の範囲なら、費用の大半は開発者の作業時間と、サーバーとデータベースの月額です。既製サービスの月額と比べるときは、自作側に障害対応と保守の人件費を足し、3年程度の総額で比べると判断がぶれません。外部の開発会社に依頼した場合の相場は、関連記事に挙げた予約システム開発の進め方の記事で、機能の範囲ごとに整理しています。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.23 テックブログ Renta!はいつ再開?不正アクセスでメールアドレス等が流出したおそれ・再開時のパスワード再設定と運営側の対応
  2. 2026.07.09 テックブログ Muse Imageとは?Metaの画像生成AIの使い方・API料金(1枚0.01ドル)と日本での提供状況【2026年10月】
  3. 2026.10.23 テックブログ Splunk 脆弱性CVE-2026-76299:影響版の確認と10.2.8などへの更新手順
  4. 2026.10.22 テックブログ Tomcatのバージョン一覧とサポート期限【2026年10月】最新版11.0.27、9.0系は2027年3月末終了・9.1系は2030年末まで
  5. 2026.10.21 テックブログ ユミルリンク不正アクセス第二報で延べ5,162万件:Cuenote FC利用企業と受信者の確認点

RELATED POSTS 関連記事

目次