GraphQL リゾルバの追加

Hono + GraphQL テンプレートは graphql-yoga を Hono の /graphql にマウントして同梱します。同じ URL に GraphiQL が含まれ、Drizzle/SQLite を背後に持つ users 型が用意されています。このレシピでは posts 型を追加して拡張します。

生成されるレイアウト

  • src/schema.graphql — 人間が編集する SDL (typeDefs にミラー)

  • src/GraphqlSchema.res — yoga が消費するインライン化された typeDefsrootValue

  • src/Resolvers/Users.res — 既存の users クエリ/ミューテーションリゾルバ

  • src/Schema.res — Drizzle の users テーブル

  • src/Server.res — yoga をマウントする Hono アプリ

Step 1 — SDL を拡張する

src/schema.graphql を編集して新しい型を追加します:

type Post {
  id: Int!
  title: String!
  body: String!
}

extend type Query {
  posts: [Post!]!
  post(id: Int!): Post
}

extend type Mutation {
  createPost(title: String!, body: String!): Post!
}

src/GraphqlSchema.restypeDefs テンプレート文字列にも同じ変更を反映してください — yoga は起動時にこのインライン SDL を読み込みます。

Step 2 — Drizzle テーブルを追加する

src/Schema.res に以下を追加します:

let posts = sqliteTable("posts", {
  "id": intCol("id", {"primaryKey": true, "autoIncrement": true}),
  "title": textCol("title", {"notNull": true}),
  "body": textCol("body", {"notNull": true}),
})
pnpm db:generate
pnpm db:migrate

Step 3 — リゾルバを書く

src/Resolvers/Posts.res を作成します:

let listPosts = async (_parent, _args, _ctx, _info) => {
  await Db.db
  ->Db.select({
    "id": Schema.posts["id"],
    "title": Schema.posts["title"],
    "body": Schema.posts["body"],
  })
  ->Db.from(Schema.posts)
  ->Db.allAsync
}

let createPost = async (_parent, args, _ctx, _info) => {
  let inserted =
    await Db.db
    ->Db.insert(Schema.posts)
    ->Db.values({"title": args["title"], "body": args["body"]})
    ->Db.returning
  inserted->Array.get(0)
}

Step 4 — rootValue に配線する

src/GraphqlSchema.res で:

let rootValue = {
  "users": Resolvers.Users.listUsers,
  "user": Resolvers.Users.userById,
  "createUser": Resolvers.Users.createUser,
  "deleteUser": Resolvers.Users.deleteUser,
  "posts": Resolvers.Posts.listPosts,
  "createPost": Resolvers.Posts.createPost,
}

Step 5 — 動作確認

pnpm dev

http://localhost:4000/graphql を開いて以下を実行します:

mutation {
  createPost(title: "Hello", body: "World") { id title }
}

query { posts { id title body } }

Step 6 — ドキュメントを再生成する

pnpm docs:graphql

これは src/schema.graphql に対して graphql-markdown を実行し、docs/schema.md (kind でグループ化) を書き出します。ドキュメントがスキーマに追従するよう、このファイルをコミットしてください。

実運用リゾルバのパターン

  • フィールドレベルのリゾルバ — 投稿の著者解決を各クエリで再取得するのではなく、Post.author 内に配置する

  • ローダーPost.author 呼び出しをバッチ化するには dataloader を使用し、@module("dataloader") でバインドする

  • コンテキスト — 認証情報や DB ハンドルはモジュールレベルの Db.db ではなく、yoga の context オプション経由で引き回す

よくある落とし穴

  • スキーマの乖離src/schema.graphql とインラインの typeDefs 文字列は一致していなければなりません。readFileSync で単一ファイルを読み込めば回避できますが、起動時にファイルシステムアクセスが必要になります。テンプレートは自己完結性を保つためインライン化しています。

  • ミューテーションが null を返す — Drizzle ビルダーで returning が呼ばれているか確認してください。insert(...).values(...) だけでは行を返しません。