社内の問い合わせを受け付けて担当者に振り分ける「社内ヘルプデスク」のような小さな業務システムを、サーバーを借りずに、Hono と Cloudflare Workers で作る連載を始めます。「サーバーレスって実際どうなの?」「軽い社内ツールを安く動かしたい」という社内エンジニアや情シス兼任の方に向けて、動くコードを1回1機能ずつ載せていきます。
「社内ツールを作りたいけど、サーバーの管理や保守まで手が回らない…」
先に結論をお伝えします。利用者が数十〜数百人規模の社内ツールなら、サーバーを持たない構成(サーバーレス)で、運用の手間をかなり減らして作れます。一方で、実行時間やデータ容量には決まった上限があるため、「向いている用途」と「向かない用途」を最初に見分けることが大切です。
この第0回では、連載で作るものの完成像、技術の選び方、そして最小の雛形が実際に動くところまでを確認します。動かすのに必要なコードと手順は、すべて記事の中に載せています。
この連載で作る社内ヘルプデスクの完成像
作るのは、社員が「PCが起動しない」「アカウントを作ってほしい」といった困りごとを登録し、情シス担当者が対応状況を管理できる仕組みです。いわゆる「チケット管理」と呼ばれるもので、問い合わせを1件ずつ「チケット」として記録します。
| 機能 | 内容 | 扱う回 |
|---|---|---|
| チケットの登録・一覧・状態管理 | 受付→対応中→解決→完了 | 第1〜2回 |
| ログインと権限 | 社員/担当者/管理者で見られる範囲を分ける | 第3回 |
| 添付ファイル | 画面のスクリーンショットなどを保存 | 第4回 |
| 通知メール | 担当者へ非同期で送信 | 第5回 |
| コメントと担当割り当て | 対応履歴を残す | 第6回 |
| 期限超過の検知 | 対応が遅れているチケットを定期チェック | 第7回 |
| 管理画面・型安全なAPI呼び出し | 画面の作り方と型の共有 | 第8〜9回 |
| ログ・監視、本番公開 | 運用に必要なことと公開手順 | 第10〜11回 |
全12回(第0〜11回)の予定です。各回のタイトルと内容は、進めながら調整する場合があります。
技術の選び方:なぜ Hono と Cloudflare Workers なのか
使う技術は次のとおりです。専門用語は、初めて出てくるときに言い換えを添えます。
| 部品 | 名称 | ひとことで言うと |
|---|---|---|
| Webの枠組み | Hono | とても軽い、Webアプリ用の部品集(フレームワーク)。Webの標準的な仕組みで書ける |
| 実行する場所 | Cloudflare Workers | サーバーを自分で用意せず、コードを預けると世界各地の拠点で動かしてくれるサービス |
| データベース | Cloudflare D1 | Workers から使える、小さめのデータベース(中身はSQLite) |
| ファイル置き場 | Cloudflare R2 | 添付ファイルなどを置くための保存場所 |
| 順番待ちの仕組み | Cloudflare Queues | 時間のかかる処理(メール送信など)を後回しにする仕組み |
| 定期実行 | Cron Triggers | 決まった時刻にコードを動かす仕組み |
選んだ理由は3つです。
- 運用の手間が小さい:OSの更新やサーバーの監視を自分でしなくてよい
- 小さく始められる:無料のプランがあり、試作の費用を抑えやすい
- これまでの連載と違う構成を見られる:当ブログではこれまで、ECS、Cloud Run、Kamal、Vercel などを取り上げてきました。今回は「サーバーレスの業務システム」の現実味を確かめます
料金や上限は、公式の資料で次のように案内されています(2026年10月時点)。
📰 出典:Cloudflare Docs「Workers Pricing」
無料プランは1日あたり10万リクエストまで、有料プラン(Workers Paid)は最低月額5米ドルからで、含まれる利用量を超えた分は従量課金です。データ容量についても、D1 は無料プランで1データベース500MB、有料プランで10GBまでと案内されています。
📰 出典:Cloudflare Docs「D1 Limits」
社内ヘルプデスクのように「文字中心のデータが数万件」程度なら、この範囲に収まりやすいはずです。ただし、料金・上限は改定されることがあるので、実際に決めるときは必ず最新の公式ページを確認してください。
雛形を動かす:最小の Hono アプリ
ここからは実装です。この回の手順は、Node.js 26(2026年10月に長期サポート版=LTS になった版。npm 11 が付属)、Hono 4、Wrangler 4(Cloudflare 公式の開発・公開用ツール)、TypeScript 6 で、空のフォルダから最後まで確認しています。npm は 11.20 以上を使ってください。古い npm 10 系では、部品のインストールが途中で止まることがありました。
(2026年10月29日 追記:記事どおりに再現できるよう、必要なコードと手順を追加しました。空のフォルダから始める手順「0. プロジェクトを用意する」、設定ファイルとテスト用ファイルの全文です。あわせて、インストールのコマンドを安全な形に改めました)
0. プロジェクトを用意する
空のフォルダから始めます。まずプロジェクトを作り、必要な部品(パッケージ)を入れます。
mkdir hono-helpdesk && cd hono-helpdesk
npm init -y
npm pkg set type=module
npm pkg set private=true --json
npm pkg set scripts.dev="wrangler dev" scripts.typecheck="tsc --noEmit" scripts.test="vitest run"
# 本番で動く部品
npm install hono@4 --ignore-scripts
# 開発のときだけ使う部品(公開用ツール・TypeScript・テスト)
npm install -D wrangler@4 typescript@6 vitest@4 @cloudflare/vitest-plugin@1 --ignore-scripts
npm init -yでpackage.json(プロジェクトの設定と、使う部品の一覧)ができます。npm pkg setは、その中身をコマンドで書き換える指定です。type=moduleはimportの書き方を使うため、scriptsはこのあと使うnpm run dev・npm run typecheck・npm testの定義です--ignore-scriptsは、部品を入れるときに部品側のスクリプトが自動で動くのを止める指定です。2025〜2026年には、この仕組みを悪用して開発者の認証情報を盗む攻撃(サプライチェーン攻撃)が続きました。今回の部品は、止めても動くことを確認しています。npm 12 では最初から止まる設定になり、必要な部品だけをnpm install-scripts approveで許可する形に変わります- 一度入れると
package-lock.json(入れた版の記録)ができます。別のパソコンで同じものを入れ直すときは、npm ci --ignore-scriptsを使うと、記録した版のまま入ります。知らないうちに新しい版を拾うことがないので、こちらが安全です
📰 出典:Unit 42「npmの脅威の状況: アタックサーフェスと緩和策」
TypeScript の設定 tsconfig.json を作ります。worker-configuration.d.ts は、あとで npx wrangler types が作る型の定義ファイルです。
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"types": ["./worker-configuration.d.ts", "@cloudflare/vitest-plugin/types"]
},
"include": ["src", "test", "worker-configuration.d.ts"]
}
Git で管理しないものを .gitignore に書きます。.dev.vars は、のちの回でメール送信用の鍵などの秘密情報を入れるファイルです。
# .gitignore
node_modules
.wrangler
.dev.vars
worker-configuration.d.ts
1. 設定ファイル:wrangler.jsonc
Workers の設定は wrangler.jsonc に書きます。ここで「どのファイルを動かすか」「どのデータベースにつなぐか」を決めます。次の内容でファイルを作ります。
// wrangler.jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "hono-helpdesk",
"main": "src/index.ts",
"compatibility_date": "2026-08-01",
"observability": { "enabled": true },
"vars": { "APP_NAME": "社内ヘルプデスク" },
"d1_databases": [
{
"binding": "DB",
"database_name": "helpdesk",
"database_id": "00000000-0000-0000-0000-000000000000",
"migrations_dir": "migrations"
}
]
}
compatibility_dateは「この日付時点の動作仕様で動かす」という指定です。ツールが対応している日付より新しい値にすると起動に失敗するため、使っているツールの対応日以前にしますbinding: "DB"は、コードからc.env.DBという名前でデータベースを使えるようにする設定ですdatabase_idはローカル開発では仮の値で動きます。本番ではwrangler d1 createで作った実際のIDに差し替えます(第11回)$schemaは、エディターで設定の入力候補を出すための指定です。observabilityは、公開後に Cloudflare 側で実行ログを見られるようにする設定です(第10回で使います)
2. データベースの最初の定義:migrations/0001_init.sql
データベースの構造を変える手順は、SQLファイルとして順番に残します。これを「マイグレーション」と呼びます。第0回では接続確認用の小さなテーブルだけを作ります。
-- migrations/0001_init.sql
CREATE TABLE IF NOT EXISTS app_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
INSERT OR IGNORE INTO app_meta (key, value) VALUES ('schema_version', '1');
3. アプリ本体:src/index.ts
Hono のアプリは、数行で書けます。
// src/index.ts
import { Hono } from 'hono'
const app = new Hono<{ Bindings: Env }>()
app.get('/', (c) => c.text(`${c.env.APP_NAME} は動いています`))
// D1 にも届くかまで確認するヘルスチェック(監視・デプロイ後の確認に使う)
app.get('/api/health', async (c) => {
const row = await c.env.DB.prepare('SELECT value FROM app_meta WHERE key = ?')
.bind('schema_version')
.first<{ value: string }>()
return c.json({ status: 'ok', schemaVersion: row?.value ?? null })
})
export default app
Env型はnpx wrangler typesでwrangler.jsoncから自動生成されます(DBやAPP_NAMEの型が付きます)/api/healthは「アプリが動いていて、データベースにも届く」ことを返す窓口です。公開後の監視や、デプロイ直後の確認に使えるので、最初に作っておくと後が楽になります
4. ローカルで動かして確認する
npx wrangler types # Env の型を作る
npx wrangler d1 migrations apply helpdesk --local # ローカルのDBにマイグレーション適用
npx wrangler dev # ローカルで起動
別の画面から curl で確認すると、次の結果が返ります。
$ curl localhost:8787/
社内ヘルプデスク は動いています
$ curl localhost:8787/api/health
{"status":"ok","schemaVersion":"1"}
--local を付けると、自分のパソコン上で動く模擬のデータベースを使うため、Cloudflare のアカウントがなくても試せます。
テストも最初から入れておく
業務システムは、作ったあとに直すことが続きます。直したときに別の場所が壊れていないか確かめるため、最初から自動テストを用意しておきます。Workers の実行環境(workerd)の上でそのままテストできる、Cloudflare 公式のテスト用プラグインを使います。
📰 出典:Cloudflare Docs「Workers Vitest integration」
テスト用の設定 vitest.config.ts を作ります。テストの前に、migrations/ の SQL をテスト用のデータベースに当てる設定も入れています。
// vitest.config.ts
import { cloudflareTest, readD1Migrations } from '@cloudflare/vitest-plugin'
import { defineConfig } from 'vitest/config'
export default defineConfig(async () => {
const migrations = await readD1Migrations('./migrations')
return {
plugins: [
cloudflareTest({
wrangler: { configPath: './wrangler.jsonc' },
miniflare: { bindings: { TEST_MIGRATIONS: migrations } },
}),
],
test: { setupFiles: ['./test/apply-migrations.ts'] },
}
})
// test/apply-migrations.ts(各テストの前に、マイグレーションを当てる)
import { applyD1Migrations } from 'cloudflare:test'
import { env } from 'cloudflare:workers'
await applyD1Migrations(env.DB, env.TEST_MIGRATIONS)
// test/env.d.ts(テスト用に vitest.config.ts から渡すバインディングの型)
declare namespace Cloudflare {
interface Env {
TEST_MIGRATIONS: import('cloudflare:test').D1Migration[]
}
}
テストは2件です。トップページと /api/health を、Workers の実行環境の上で呼び出して確かめます。
// test/health.test.ts
import { SELF } from 'cloudflare:test'
import { describe, expect, it } from 'vitest'
describe('ヘルスチェック', () => {
it('トップページが 200 を返す', async () => {
const res = await SELF.fetch('http://example.com/')
expect(res.status).toBe(200)
expect(await res.text()).toContain('社内ヘルプデスク')
})
it('/api/health が D1 のスキーマ版を返す', async () => {
const res = await SELF.fetch('http://example.com/api/health')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ status: 'ok', schemaVersion: '1' })
})
})
npm test で2件のテストが通ることを確認しました(npm run typecheck による型チェックも通過)。
つまずきやすい点
実際に作業して、次の3点でつまずきました。
compatibility_dateが新しすぎるとテストが起動しない:「この環境が対応する日付は◯◯まで」というエラーになります。設定の日付を少し前にして解決しました- テスト用の部品の名前が変わっていた:以前の
@cloudflare/vitest-pool-workersは@cloudflare/vitest-pluginに改名され、旧名は更新終了の案内が出ます。新しい名前を使いました - 古い npm 10 系だと
npm installが止まる:Cannot read properties of null (reading 'edgesOut')というエラーで止まりました。部品同士の依存関係を解決する処理の不具合で、npm 11.20 以上では起きません。--legacy-peer-depsを付けても通りますが、依存関係のチェックを省く指定なので、npm(Node.js)を新しくする方をおすすめします
なお、今回の環境では Cloudflare のアカウントを使った実際の公開(wrangler deploy)は行っていません。実環境への公開は未確認で、第11回で手順を扱います。
発注者向けメモ:サーバーレスの社内ツールを頼むときの確認点
この連載は主に技術者向けですが、開発会社にサーバーレス構成を提案されたときの確認点をまとめます。
| 確認すること | 理由・見るポイント |
|---|---|
| 利用者数・データ量・ファイルの大きさの見込みを伝えたか | 無料・有料プランの上限に収まるかで、構成と月額費用が変わる |
| 月額費用の内訳(実行回数・データ容量・保存量)を説明してもらえるか | 従量課金なので、利用が増えたときの費用の見当が必要 |
| アカウントの持ち主は誰か | Cloudflare アカウントが開発会社名義だと、契約終了時に引き継げなくなる。発注側名義で作るか、移管手順を決めておく |
| 時間のかかる処理(大量の帳票出力など)がないか | 1回の実行時間に上限があるため、向かない処理がある |
| 障害時や上限超過時の連絡・対応の範囲 | 運用の責任分担を契約前に確認する |
「サーバーがないから保守が要らない」わけではありません。アプリ自体の修正、部品の更新、設定やアカウントの管理は引き続き必要です。保守の範囲と費用は、見積もりの段階で確認しておきましょう。
まとめと次回予告
この回では、次のことを確認しました。
- 社内ヘルプデスクを、Hono+Cloudflare Workers で作る連載の完成像
- サーバーレス構成の特徴と、費用・上限は公式ページで確認する必要があること
wrangler.jsonc・マイグレーション・Hono アプリ・テストの最小構成が、ローカルで動くこと
次回の第1回では、チケットを登録する API を作ります。データベースにチケットの表を作り、入力内容をZodで検証して保存するところまで進めます。
この連載の記事一覧
この記事は連載「Honoで作る社内ヘルプデスク」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。
- 【Honoで作る社内ヘルプデスク 第0回】連載の全体像と、Hono+Cloudflare Workersの雛形を動かす(この記事)
- 【Honoで作る社内ヘルプデスク 第1回】チケット登録APIを作る:D1のマイグレーションとZodの入力チェック
- 【Honoで作る社内ヘルプデスク 第2回】チケットの一覧と状態遷移を作る:ページングと「戻せない状態」のルール
- 【Honoで作る社内ヘルプデスク 第3回】ログインと権限を付ける:パスワードの保存・Cookie・「社員/担当者/管理者」の線引き










コメント
コメント一覧 (3件)
[…] 【Honoで作る社内ヘルプデスク 第0回】連載の全体像と、Hono+Cloudflare Workers… […]
[…] 【Honoで作る社内ヘルプデスク 第0回】連載の全体像と、Hono+Cloudflare Workers… 社内ヘルプデスクをHonoとCloudflare […]
[…] 【Honoで作る社内ヘルプデスク 第0回】連載の全体像と、Hono+Cloudflare Workers… […]