1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【学習】TS×honoで、安全な開発と設計を意識したサンプルAPIを作った

1
Last updated at Posted at 2026-04-07

はじめに

こんにちは!!

最近は 超かぐや姫 を見て感動したり、
ゾンビランドサガのライブに行ったり、
胃腸炎にかかって苦しんだりしていた @hayatohanaoka です!!

安全な開発とクリーンアーキテクチャ寄りの設計 をアウトプットするため、TypeScript + Hono でサンプル API を作りました。

単体テスト・E2E(WireMock)・Docker / Kubernetes(Helm / Skaffold)・GitHub Actions CI まで一通り揃えています。
※ API の公開はしておらず、ローカルで動かす前提です。

Hono 自体の解説は割愛しますが、Hono がリクエストをどう処理しているかは過去記事で触れているので、興味のある方はこちらもどうぞ。

この記事では、設計で意識した点や技術スタック、テスト・CI まわりの構成を紹介します。

ソースコード

どんな API か

Qiita や Zenn など複数の記事サービスの API を裏で叩き、結果を集約して返す API です。クエリパラメータによる検索も実装しました。

また、外部 API の呼び出し先は環境変数(QIITA_URL / ZENN_URL)で切り替えられるため、本番では実際のサービスへ、テスト時は WireMock へ向けるといった使い分けができます。

エンドポイント一覧

メソッド パス できること
GET /v1/systems/ping ヘルスチェック(pong を返す)
GET /api/v1/articles 複数サービスの記事を集約して一覧取得
GET /api/v1/articles?q=… キーワードで記事を横断検索

レスポンスサンプル

/api/v1/systems/ping
$ curl -s localhost:13000/v1/systems/ping
pong
/api/v1/articles

※ body がかなり大きいので、一部抜粋

$ curl -s localhost:13000/api/v1/articles
{
  "articles": [
    {
      "title": "【Java入門】完全修飾クラス名とは?長ったらしい名前の正体と使い方を分かりやすく解説🌱",
      "url": "https://qiita.com/kamoi-kenichi/items/9dc9892f710c852616eb"
    },
    {
      "title": "ANSYSメッシュ作成5つのポイント",
      "url": "https://qiita.com/EdgeDevice/items/f8d71c929985c322a2fa"
    },
    (...中略...)
    {
      "title": "GitHub Copilot は自ら学ぶ: Copilot Memory 入門",
      "url": "https://zenn.dev/microsoft/articles/50863342150992"
    },
    {
      "title": "AIがコードを書くほど、要件定義は上に移動する――Spec・Context・Harness三層設計",
      "url": "https://zenn.dev/gvatech_blog/articles/30f79910d111bb"
    }
  ]
}
/api/v1/articles

※ body がかなり大きいので、一部抜粋

$ curl -s localhost:13000/api/v1/articles?q=claude
{
  "articles": [
    {
      "title": "Emacs から Claude Code を使う helm の設定を書いた",
      "url": "https://qiita.com/mori-dev@github/items/4ae373730eb4b71caae9"
    },
    {
      "title": "Claude Codeで安全にバイブコーディングするためのセキュリティガイド【個人・チーム開発対応 / コピペで社内展開OK】",
      "url": "https://qiita.com/kotaro_ai_lab/items/af25eb6608ff58893c74"
    },
    (...中略...)
    {
      "title": "AI駆動開発の実践(1)CLAUDE.mdとコンテキスト戦略 — AIに「現場」を伝える技術",
      "url": "https://zenn.dev/miyan/articles/ai-driven-dev-claude-md-context"
    },
    {
      "title": "🔍Claude CodeのSkill作成中に見かけた user-invocableを調べてみた",
      "url": "https://zenn.dev/dely_jp/articles/76842757cce7b6"
    }
  ]
}

意識したこと

TDD をしやすくする

単体テスト(app)と E2E(e2e)を、先に・繰り返し実行できるようにしています。外部の Qiita / Zenn 相当の応答は WireMock で固定し、Docker(CI)や Skaffold + Helm(ローカル Kubernetes でモックだけ立てる、など)と組み合わせて再現可能にしています。

CI でエラーに気づく仕組みを作る

main 向けの push / PR で、単体 → E2E(WireMock コンテナ + API 起動)→ コンテナイメージビルド(Skaffold)までを GitHub Actions で実行します。

E2E フィクスチャの分離

  • 外部 API の応答は テストコードに直書きせず、WireMock 用のマッピング JSON を e2e/fixtures/ に置く。
  • テストファイルのパスから fixture ディレクトリを自動解決する規約を採用。e2e/tests/ 配下のパスを e2e/fixtures/ へ読み替え、サービスごとのサブディレクトリ(qiita/zenn/)を走査する(例: e2e/tests/api/v1/articles/get.test.tse2e/fixtures/api/v1/articles/get/{qiita,zenn}/*.json)。
  • テスト側は beforeEachresetAllStubs()setUpStubs(import.meta.filename)e2e/src/setup-wiremock.ts)を呼ぶだけで、対応する fixture が WireMock Admin API に流し込まれる。
  • 期待するレスポンスの大きな JSON をテスト本体から切り離し、ケース追加・差分レビューをしやすくする。

クリーンアーキテクチャ寄りの設計

ドメイン・ユースケース・ポート・ゲートウェイ・ドライバ(外部 HTTP)・REST(Hono)を分け、dependencies.ts で組み立てています。

技術スタック

領域 採用技術
ランタイム Node.js(Docker イメージは Node 24 Alpine ベース)
言語 TypeScript(module / moduleResolution: NodeNext)
Web フレームワーク Hono
Node サーバー @hono/node-server
開発時実行 tsxpnpm dev でウォッチ起動)
パッケージマネージャ pnpm
単体テスト Vitestappsrc/**/*.test.ts
E2E Viteste2e
外部 API モック WireMock(Docker 公式イメージ)
コンテナ Docker(マルチステージビルド)
Kubernetes Helm
ビルド・デプロイ連携 Skaffold
CI GitHub Actions

ディレクトリ構成

パス 内容
app/ Hono API のソース、単体テスト、pnpm-lock.yaml
e2e/ E2E(Vitest)、WireMock 連携、fixtures/ にマッピング JSON
environment/ Dockerfile、Skaffold、Helm、WireMock 用イメージ定義など

CI(GitHub Actions)

main への push または pull request で、app/** に変更があるときにワークフローが走ります。

  1. app で依存関係を入れ、Vitest で単体テスト
  2. Docker で WireMock を 2 つ起動し、API をバックグラウンドで起動したうえで e2epnpm vitest run
  3. environment で Skaffold により Docker イメージをビルド

手動実行は workflow_dispatch からも可能です。

Docker / Kubernetes

項目 場所・内容
Dockerfile environment/app/Dockerfile(Skaffold 上のビルドコンテキストは app/
Skaffold environment/skaffold.yaml — ビルド後、Helm で hono-api をデプロイ可能
WireMock 連携 with-mock プロファイルで WireMock 用イメージと Helm リリースを追加
本番向け調整 prd プロファイルでレプリカ数などを上書き

クラスタ固有の設定やシークレットは、利用環境に合わせて Helm チャートを拡張してください。

Skaffold コマンドの使い分け

いずれも environment ディレクトリで実行します。ローカル Kubernetes と Skaffold が前提です。

目的 コマンド 補足
イメージだけビルド skaffold build 主に CI と同様の用途。コンテナイメージのビルドのみで、アプリケーションは起動しません。
E2E 用・モックだけ k8s skaffold dev --port-forward API 本体は Kubernetes では動かさず、モックサーバー(WireMock)をローカルクラスタ上に立てます。API は別途 apppnpm dev などとして起動し、ポートフォワードしたモックの URL を QIITA_URL / ZENN_URL に向けてから E2E を実行します。
アプリだけ k8s skaffold run --port-forward ローカル k8s で API だけデプロイするとき。モック用の依存サービスはクラスタ上では立てず、Helm のデフォルトどおり本番相当の URL へリクエストが飛びます。

skaffold dev で API を Kubernetes に載せずモックだけにしているのは、ローカル実行のほうが変更の反映が速いことに加え、本番用の Deployment に開発専用の volume 定義を混ぜたくないためです。

今後の伸び代

現状の CI では、E2E 用の WireMock を Docker コマンドで直接起動しています。ローカルでは Skaffold + Helm で管理できているのに対し、CI だけ別の手段になっている点が課題です。

改善の方向としては 2 通り考えています。

  • GitHub Actions 内で skaffold dev --port-forward を実行する — ローカルと同じ Helm チャートで WireMock を立てられるため、管理の一貫性が保てる
  • E2E 実行用のクラスタを別途用意する(クラウド or Actions 内部の k8s) — E2E リソースをそこにデプロイする形にすれば、CI ワークフロー自体はシンプルになる

いずれも「ローカルと CI で WireMock の立て方を揃える」ことがゴールです。

まとめ

TypeScript + Hono で、安全な開発とクリーンアーキテクチャ寄り設計を意識した API を作りました。

  • TDD をしやすくするために、外部 API を WireMock でモックし、単体テストと E2E の両方をローカルで完結できるようにした
  • CI でエラーに気づくために、GitHub Actions で単体テスト → E2E → イメージビルドまで自動実行するようにした
  • E2E フィクスチャはテストコードから分離し、マッピング JSON として管理することで、ケース追加や差分レビューをしやすくした
  • Docker / Kubernetes(Helm / Skaffold)を使い、ローカルでもコンテナ上でも同じ構成で動かせるようにした

上記の意識したことが少しでも伝わると嬉しいですし、業務ないしは個人開発をしようとしている方の参考になれたら嬉しいです!

Hono 自体は軽量ですし、フレームワーク特有の書き方のクセとかもないため、非常に書きやすかったです。
フレームワークの設計上、クリーンアーキテクチャ寄りの設計がし難いものもある中で、ここまで自由度高く書けるのはとても良いですね。
個人開発、マイクロサービスという観点に置いては、今後の技術選定の選択肢としても良いと思いました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?