Full-Stack (single package)¶
Hono Vite+/React Drizzle Validation Shared Types
1 つの package.json で完結するフルスタック ReScript アプリケーション。サーバー(SQLite 上の Hono + Drizzle)とクライアント(Vite+/React)が src/server/ と src/client/ 配下に並んで配置され、3 つ目のソースルート src/shared/ に両側が import する型を置く。共有型と単一プロセスの dev ループを望み、Monorepo (Hono + React) テンプレートが要求するワークスペースツーリングのコストを払いたくない場合に、このテンプレートを選ぶ。
ウィザードはさらに API strategy という軸を提示する: REST(手書きの Hono ルートとfetch クライアント)か GraphQL(/graphql にマウントされる graphql-yoga とクライアント側の rescript-relay)。共有インフラ —— Drizzle スキーマ、libsql クライアント、vitest セットアップ、Vite+ プロキシ、Shared.res —— はどちらのモードでも同一である。
生成内容 (REST)¶
my-app/
├── rescript.json # sources: src/shared, src/server, src/client (subdirs)
├── package.json # ESM, bundles server + client deps
├── index.html # Vite entry — loads /src/client/ClientMain.res.mjs
├── vite.config.mjs # Vite+ + react plugin + /api proxy
├── drizzle.config.ts
├── vitest.config.mjs # loads vitest.setup.mjs
├── vitest.setup.mjs # pins DATABASE_URL=:memory:
├── .env.example # DATABASE_URL=file:./data/app.db
├── src/
│ ├── shared/Shared.res # nested modules: Shared.Types.* / Shared.Api.*
│ ├── server/
│ │ ├── ServerMain.res # entry — Server.start()
│ │ ├── Server.res # Hono app + GET /api/health + Routes.Users.register
│ │ ├── Routes.res # nested: module Users { let register = app => ... }
│ │ ├── Schema.res # Drizzle SQLite schema (users)
│ │ ├── Db.res # libsql client + helpers
│ │ ├── Validation.res # zod or sury — selected in the wizard
│ │ ├── Hono.res / HonoNodeServer.res
│ │ └── __tests__/Server.test.mjs # vitest: app.request("/api/health") returns 200
│ └── client/
│ ├── ClientMain.res # React root render
│ ├── App.res # users form + list (uses Shared.Types.user)
│ ├── ApiClient.res # fetch wrapper, types pinned to Shared.Api.*
│ └── __tests__/Api.test.mjs
├── README.md # Architecture + Shared Types + Database + Layout sections
├── LICENSE # MIT, holder = project name
├── .nvmrc # Node 24
├── .gitignore # adds data/, dist/, .vite/, drizzle/, .env
├── .editorconfig
└── .github/
├── dependabot.yml
└── workflows/ci.yml # install + vitest
GraphQL バリアントでは src/server/Yoga.res、src/server/GraphqlSchema.res、src/server/Resolvers.res、src/server/schema.graphql、src/client/RelayEnvironment.res、src/client/UsersListQuery.res、relay.config.js が追加され、src/client/__generated__/ が gitignore され、graphql、graphql-yoga、rescript-relay、relay-compiler が宣言され、relay/relay:watch のスクリプトペアと、dev に追加の npm:relay:watch トークンが追加される。
ウィザードオプション¶
オプション |
効果 |
|---|---|
Project name |
npm の |
Package manager |
npm / pnpm / yarn / bun のいずれか。 |
Validation library |
|
API strategy |
REST ↔ GraphQL。サーバー/クライアントの形(手書きの Hono ルート + fetch か、graphql-yoga + rescript-relay か)を選び、依存・スクリプト・rescript.json の |
主要な依存 (REST)¶
パッケージ |
用途 |
バージョン |
|---|---|---|
|
ReScript コンパイラ+ランタイム |
|
|
React バインディング |
|
|
React ランタイム |
|
|
HTTP フレームワーク |
|
|
Node HTTP アダプタ |
|
|
バリデーションバックエンド |
|
|
libsql クライアント(SQLite 互換、Turso 対応) |
|
|
型安全な SQL ビルダー |
|
|
マイグレーションジェネレータ |
|
|
Vite+ ビルドツール+クラシック Vite のフォールバック |
|
|
React fast-refresh |
|
|
テストランナー+カバレッジ |
|
|
|
|
GraphQL バリアントで追加されるもの¶
パッケージ |
用途 |
バージョン |
|---|---|---|
|
リファレンス実装。 |
|
|
|
|
|
Relay ランタイム向けの ReScript バインディング+ppx |
|
|
|
|
主要なファイル¶
src/server/Server.res (REST)¶
Hono アプリの骨組み。ヘルスチェックルートはインラインに置かれ、リソース別のルートは src/server/Routes.res に配置され、小さな規約を介して自分自身を登録する:
let app = Hono.createApp()
app->Hono.onError((err, ctx) => {
Console.error(err)
ctx->Hono.status(500)->Hono.json({"error": "Internal Server Error"})
})
app->Hono.get("/api/health", ctx => ctx->Hono.json({"status": "ok"}))
Routes.Users.register(app)
let start = () => {
HonoNodeServer.serve({fetch: app->HonoNodeServer.honoFetch, port: 3000})
Console.log("Server on http://localhost:3000 — try /api/health")
}
新しいリソースを追加するには、Routes.res に兄弟モジュール(module Posts = { let register = app => ... })を追加し、ここに Routes.Posts.register(app) の呼び出しを 1 行追加する。
src/server/Routes.res¶
実際のハンドラロジックを保持する。同梱の Users グループは Drizzle のフルラウンドトリップを実行する(select → 行を返す、insert → バリデーション失敗時は 400、成功時は 201 と挿入された行を返す)。
src/server/Validation.res¶
parseCreateUserReq: JSON.t => result<createUserReq, string> —— POST /api/users のランタイムコントラクト。シグネチャは zod / sury のどちらのバリアントでも同一なので、呼び出し側がどちらのライブラリが採用されたかで分岐する必要はない。
src/server/ServerMain.res¶
中身は Server.start() の 1 行のみ。この分割により、テストが Server.res.mjs を import する前に vitest.setup.mjs で DATABASE_URL=:memory: を固定でき、ポートにバインドせずに app.request("/api/health") を呼び出せる:
// vitest.setup.mjs
process.env.DATABASE_URL = ":memory:";
src/client/App.res¶
小さなユーザーフォームとリスト。ワイヤフォーマット型が明示されているため、src/shared/Shared.res のずれは即座に現れる:
let req: Shared.Api.createUserReq = {name, email}
let _ = await ApiClient.createUser(req)
src/client/ApiClient.res¶
薄い fetch ラッパ。すべてのルートが /api/* 配下にあるので、vite.config.mjs のVite+ プロキシが dev 時に Hono サーバーへ転送できる(CORS は不要)。本番環境では同じサーバーが両方をホストする。
vite.config.mjs¶
ProjectFileBuilders.viteConfigWithProxy を介して生成される —— Vite+ + React fast-refresh + Hono サーバーをターゲットとする /api/* プロキシ。デプロイを 2 つのオリジンに分割する場合は、プロキシのターゲットを編集すること。
GraphQL バリアント: src/server/GraphqlSchema.res, Resolvers.res, schema.graphql¶
GraphQL スキーマは同期を保つべき 2 箇所に存在する: src/server/schema.graphql の SDL(ビルド時に Relay コンパイラが読む)と、src/server/GraphqlSchema.res 内にインライン展開された typeDefs 文字列(ランタイムに graphql-yoga が読む)。リゾルバは src/server/Resolvers.res 配下にネストされたモジュール(例えば module Users)として整理されている。
GraphQL バリアント: src/client/RelayEnvironment.res, UsersListQuery.res¶
RelayEnvironment.res は Relay クライアントを設定する。クエリは %relay() タグ(例: module UsersListQuery = %relay( ... ))として書かれる。クエリを編集した後は relay-compiler --watch を実行して src/client/__generated__/<Query>_graphql.res を再生成する。これらの生成ファイルは gitignore 対象である。
npm スクリプト (REST)¶
スクリプト |
説明 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
npm スクリプト(GraphQL で追加されるもの)¶
スクリプト |
説明 |
|---|---|
|
concurrently のファンアウトに |
|
|
|
|
2日目以降のレシピ¶
Hono エンドポイントの追加 ——
src/server/Routes.res配下に型付きの POST/GET ルートを追加するGraphQL リゾルバの追加 —— リゾルバと対応する
%relay()クエリを追加する(GraphQL バリアント)Drizzle のセットアップ —— Drizzle スキーマ(
src/server/Schema.res)を深掘りするReact コンポーネントの作成 —— クライアント側に新しい React コンポーネントを追加する
OpenAPI ドキュメントの追加 —— Hono + zod スキーマから OpenAPI ドキュメントを生成する(REST バリアント)
プロジェクトを開いた後の ReScript 側のエディタワークフローについては 機能概要 を参照する。
補足¶
1 つの
package.jsonで世界を覆う。 サーバー・クライアント・共有の各ルートは、3 つのsourcesエントリを持つ単一のrescript.jsonを介してコンパイルされる。npm installを 1 回実行するだけで、すべての依存関係がインストールされる。devはconcurrently経由で 3 つ(GraphQL の場合は 4 つ)のプロセスを起動する。 内訳はrescript -w、node --watch src/server/ServerMain.res.mjs、vp dev、そして(GraphQL のみ)relay-compiler --watchである。devの初回実行時にサーバー起動が失敗する場合、rescript -wがまだ最初の.res.mjsを生成していないことを意味する —— 1 サイクル待つかdevを再実行する。Vite+ は
/api/*(GraphQL バリアントでは/graphqlも)を Hono サーバーへプロキシし、dev 時にブラウザリクエストを同一オリジンに保つ。CORS はデフォルトで無効である。クライアントとサーバーを別ホスティングでデプロイする前に、src/server/Server.res冒頭のHono.cors(...)ブロックのコメントを解除し、オリジンの許可リストを固定すること。Shared.resはsrc/shared/に置かれている ——src/shared/Shared.res配下のネストモジュールが依存方向を明確に保つためである。両側がSharedに依存し、Sharedはどちらにも依存しない。record フィールドをリネームすると、両側が再コンパイルされる。vitest.setup.mjsはDATABASE_URL=:memory:を固定する —— テストがServer.res.mjsを import する前に実行される。コミット済みの.env.exampleは本番形のデフォルト(file:./data/app.db)を示す。data/、dist/、.vite/、drizzle/、.env(および GraphQL バリアントではsrc/client/__generated__/)は gitignore 対象である。GraphQL は 1 度きりのブートストラップ手順を含む: 初回ビルドの前に
pnpm relay(またはnpm run relay)を 1 度は実行して、src/client/__generated__/を生成しておく必要がある。devスクリプトはrelay:watchを起動し続けるので、初回サイクル以降は意識する必要がない。Vite+ は 1.0 以前のリリースである。 リリースで動かなくなった場合は、
vite.config.mjsのvite-plusをviteに置き換え、vpスクリプトをviteに置き換えること。クラシックな Vite 依存はすでにフォールバックとして宣言されている。